Python Scrapy Selenium整合:启动浏览器并登陆

某些网站要求用户必须先登录,然后才能获取网络数据,这样爬虫程序将无法随意爬取数据。

为了登录该网站,通常有两种做法:
  1. 直接用爬虫程序向网站的登录处理程序提交请求,将用户名、密码、验证码等作为请求参数,登录成功后记录登录后的 Cookie 数据。
  2. 使用真正的浏览器来模拟登录,然后记录浏览器登录之后的 Cookie 数据。

上面两种方式的目的是一样的,都是为了登录目标网站,记录登录后的 Cookie 数据。但这两种方式各有优缺点:
  • 第一种方式需要爬虫开发人员自己来处理网站登录、Cookie 管理等复杂行为。这种方式的优点是完全由自己来控制程序,因此爬虫效率高、灵活性好;缺点是编程麻烦,尤其是当目标网站有非常强的反爬虫机制时,爬虫开发人员要花费大量的时间来处理。
  • 第二种方式则完全使用真正的浏览器(比如 Firefox、Chrome 等)来模拟登录。这种方式的优点是简单、易用,而且几乎可以轻松登录所有网站(因为本来就是用浏览器登录的,正常用户怎么访问,爬虫启动的浏览器也怎么访问);缺点是需要启动浏览器,用浏览器加载页面,因此效率较低。

在使用 Scrapy 开发爬虫程序时,经常会整合 Selenium 来启动浏览器登录。

需要指出的是,Selenium 本身与爬虫并没有多大的关系,Selenium 开始主要是作为 Web 应用的自动化测试工具来使用的,广大 Java 开发人员对 Selenium(开始是用 Java 写成的)应该非常熟悉。Selenium 可以驱动浏览器对 Web 应用进行测试,就像真正的用户在使用浏览器测试 Web 应用一样。后来的爬虫程序正是借助于 Selenium 的这个功能来驱动浏览器登录 Web 应用的。

为了在 Python 程序中使用 Selenium,需要以下 3 步:
  1. 为 Python 安装 Selenium 库。运行如下命令,即可安装 Selenium:

    pip install selenium

    运行上面命令,安装成功后将会看到如下提示信息:

    Installing collected packages: selenium
    Successfully installed selenium-3.14.0

  2. 为 Selenium 下载对应的浏览器驱动。Selenium 支持 Chrome、Firefox、Edge、Safari 等各种主流的浏览器,登录 https://selenium-python.readthedocs.io/installation.html#drivers 即可看到各浏览器驱动的下载链接。
    本章我们将驱动 Firefox 来模拟登录,因此,通过其页面的链接来下载 Firefox 对应的驱动(对于 32 位操作系统,下载 32 位的驱动;对于 64 位操作系统,下载 64 位的驱动)。下载完成后将得到一个压缩包,解压该压缩包将得到一个 geckodriver.exe 文件,可以将该文件放在任意目录下,本项目将该驱动文件直接放在项目目录下。
  3. 安装目标浏览器。比如本项目需要启动 Firefox 浏览器,那么就需要在目标机器上安装 Firefox 浏览器。
    除安装 Firefox浏览器之外,还应该将 Firefox 浏览器的可执行程序(firefox.exe)所在的目录添加到 PATH 环境变量中,以便 Selenium 能找到该浏览器。

经过上面 3 步,Python 程序即可使用 Selenium 来启动 Firefox 浏览器,并驱动 Firefox 浏览目标网站。

此处使用如下简单的程序进行测试。
from selenium import webdriver
import time

# 通过executable_path指定浏览器驱动的路径
browser = webdriver.Firefox(executable_path="WeiboSpider/geckodriver.exe")
# 等待3秒,用于等待浏览器启动完成
time.sleep(3)
# 浏览指定网页
browser.get("http://c.biancheng.net")
# 暂停5秒
time.sleep(5)
如果成功安装了 Selenium,并成功加载了 Firefox 浏览器驱动,且 Firefox 的可执行程序所在的目录位于 PATH 环境变量中,运行上面程序,Firefox 浏览器将会被启动,并自动访问 http://c.biancheng.net 站点。

在成功安装了 Selenium、驱动及目标浏览器之后,接下来我们在 Scrapy 项目中整合 Selenium,通过 Scrapy+Selenium 来登录 weibo.com。

按照惯例,首先创建一个 Scrapy 项目。在命令行窗口中执行如下命令:

scrapy startproject WeiboSpider

然后在命令行窗口中进入 WeiboSpider 所在的目录下(不要进入 WeiboSpider\WeiboSpider 目录下),执行如下命令来生成 Spider 类:

scrapy genspider weibo_post "weibo.com"

上面两个命令执行完成后,一个简单的 Scrapy 项目就创建好了。

接下来需要修改 WeiboSpider\items.py、WeiboSpider\pipelines.py、WeiboSpider\spiders\weibo_post.py、WeiboSpider\settings.py 文件,将它们全部改为使用 UTF-8 字符集来保存。

本项目不再重复介绍使用 Scrapy 爬取普通文本内容的方法,而是重点介绍在 Scrapy 项目中整合 Selenium的方法,因此不需要修改 items.py 和 pipelines.py 文件。

本项目直接修改 weibo_post.py 文件,在 Spider 类中整合 Selenium 调用 Firefox 登录 weibo.com,接下来爬虫程序即可利用登录后的 Cookie 数据来访问 weibo 内容。

使用 Selenium 调用 Firefox 登录 weibo.com,首先肯定要对 weibo.com 的登录页面进行分析,不过前面两个项目己经详细介绍了这种分析过程,故此处直接给出分析结果:
  • weibo 的登录页面是:https://weibo.com/login/。
  • 在登录页面中输入用户名的文本框是://input[@id="loginname"] 节点。
  • 在登录页面中输入密码的文本框是://input[@type="password"] 节点。
  • 在登录页面中登录按钮是://a[@node-type="submitBtn"] 节点。

通过分析得到以上内容之后,接下来可以在 Spider 类中额外定义一个方法来使用 Selenimn 调用 Firefox 登录 weibo.com。该 Spider 类的代码如下:
import scrapy
from selenium import webdriver
import time

class WeiboPostSpider(scrapy.Spider):
    name = 'weibo_post'
    allowed_domains = ['weibo.com']
    start_urls = ['http://weibo.com/']
    def __init__(self):
        # 定义保存登录成功之后的cookie的变量
        self.login_cookies = []
    # 定义发送请求的请求头
    headers = {
        "Referer": "https://weibo.com/login/",
        'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"
    }
    def get_cookies(self):
        '''使用Selenium模拟浏览器登录并获取cookies'''
        cookies = []
        browser = webdriver.Firefox(executable_path="geckodriver.exe")
        # 等待3秒,用于等待浏览器启动完成,否则可能报错
        time.sleep(3)
        browser.get("https://weibo.com/login/")  #①
        # 获取输入用户名的文本框
        elem_user = browser.find_element_by_xpath('//input[@id="loginname"]')
        # 模拟输入用户名
        elem_user.send_keys('xxxxxx@sina.com') #②
        # 获取输入密码的文本框
        elem_pwd = browser.find_element_by_xpath('//input[@type="password"]')
        # 模拟输入密码
        elem_pwd.send_keys('yyyyyy')  #③
        # 获取提交按钮
        commit = browser.find_element_by_xpath('//a[@node-type="submitBtn"]')
        # 模拟单击提交按钮
        commit.click()  #④
        # 暂停10秒,等待浏览器登录完成
        time.sleep(10)
        #登录成功后获取cookie
        if "微博-随时随地发现新鲜事" in browser.title:
            self.login_cookies = browser.get_cookies()
        else:
            print("登录失败!")
    # start_requests方法会在parse方法之前执行,该方法可用于处理登录逻辑。
    def start_requests(self):
        self.get_cookies()
        print('=====================', self.login_cookies)
        # 开始访问登录后的内容
        return [scrapy.Request('https://weibo.com/cyuyanzhongwenwang/',
            headers=self.headers,
            cookies=self.login_cookies,
            callback=self.parse)]

    # 解析服务器相应的内容
    def parse(self, response):
        print('~~~~~~~parse~~~~~')
        print("是否解析成功:", in response.text)
上面程序中 ① 号代码控制 Firefox 打开 weibo.com 的登录页面:https://weibo.com/login/;② 号代码控制 Firefox 在登录页面的用户名文本框中输入用户名;③ 号代码控制 Firefox 在登录页面的密码文本框中输入密码:④ 号代码模拟用户单击登录页面中的“登录”按钮。

上面 Spider 程序重写了两个方法,start_requests(self) 和 parse(self, response),其中 start_request(self) 方法会在 Scrapy 发送请求之前执行,该方法中调用 self.get_cookies() 方法来登录 weibo.com,并保存登录之后的 Cookie 数据,这样该爬虫程序即可成功访问登录之后的 https://weibo.com/cyuyanzhongwenwang/ 页面(这是我的 weibo 主页,读者在测试时应换成登录账户对应的主页)。

本项目的 parse(self, response) 方法并未 yield item,只是简单地判断了 response 中是否包含登录账号信息,因为本项目只是示范在 Scrapy 项目中如何整合 Selenium 进行登录,至于登录之后如何提取信息,前面两个项目己多次介绍,故本项目不再重复讲解。

接下来依然需要对 settings.py 文件进行修改,增加一些自定义请求头(用于模拟浏览器),设置启用指定的 Pipeline。下面是本项目修改后的 settings.py 文件:

BOT_NAME = 'WeiboSpider'

SPIDER_MODULES = ['WeiboSpider.spiders']
NEWSPIDER_MODULE = 'WeiboSpider.spiders'

ROBOTSTXT_OBEY = False

# 配置默认的请求头
DEFAULT_REQUEST_HEADERS = {
    "User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}

# 配置使用Pipeline
ITEM_PIPELINES = {
    'WeiboSpider.pipelines.WeibospiderPipeline': 300,
}

留心上面的 ROBOTSTXT_OBEY 配置,这行配置代码指定该爬虫程序不遵守该站点下的 robot.txt 规则文件(Scrapy 默认遵守 robot.txt 规则文件),强行爬取该站点的内容。

经过上面配置,接下来在 WeiboSpider 目录下执行如下命令来启动爬虫。

scrapy crawl weibo_post

运行该爬虫程序,即可看到它会自动启动 Firefox 来登录 weibo.com ,并可以在命令行窗口中看到对应解析成功的输出信息。

从该爬虫程序的运行过程来看,整合 Selenium 之后 Scrapy 的运行速度明显慢了很多。因此,Scrapy 通常只使用 Selenium 控制浏览器执行登录,不会使用 Selenium 控制浏览器执行普通下载,普通下载使用 Scrapy 自己的下载中间件即可(效率更高)。

一句话,只要技术到位,网络上没有爬取不到的数据。当然,如果有些网站的数据属于机密数据,并且这些网站也已经采取种种措施来阻止非法访问,但是你非要越过层层限制去访问这些数据,这就涉嫌触犯法律了。因此,爬虫也要适可而止。