备注
本篇博客主要参考了图灵程序设计丛书:
- 《Python网络爬虫权威指南》(Web Scraping with Python, 2E)
作者:【美】瑞安·米切尔
注意
大多数主流网站都会在它们的 robots.txt 文件里注明禁止爬虫接入的 url,这和相关法律责任有关,爬虫时需要注意各种细节!!穿越网页表单与登录窗口进行抓取
Web 正在朝着页面交互、社交媒体等趋势不断演进,表单和登录窗口成了许多网站不可或缺的一部分
以下会举几个例子,利用爬虫和 Web 服务器进行数据交互
Python Requests 库
Requests 库是由 Kenneth Reitz 创建的(同时他还创建了 pipenv 工具)
这是一个擅长处理 HTTP 请求、cookie、header 等内容的 Python 第三方库
使用pip
就可以简单安装上
1 | pip install requests |
提交一个基本调单
安全起见,就以书上提供的网站来举例 http://pythonscraping.com/pages/files/form.html
网站页面如图
接着打开浏览器检查器,可以看到网页源码里的<form>
标签
1 | <form method="post" action="processing.php"> |
两个输入字段的名称name
是:
firstname
lastname
这两个字段的名称决定了表单提交后要被 POST
到服务器上的可变参数的名称
所以要模拟表单提交数据的行为,变量名称就要与字段名称对应
其次,表单的操作发生在 processing.php (绝对路径就是 http://pythonscraping.com/pages/files/processing.php)
requests
库提交表单很简单,代码如下
1 | paras = { |
对于大多数网站来说,关注其 <form>
元素里的 action
字段,字段内容为表单提交后网站会显示的页面 URL
单选按钮、复选框和其他输入
HTML 标准里提供了大量可用的表单输入字段
- 单选按钮
- 复选框
- 下拉选框
- 等等
HTML5 还增加了其他控件
- 滚动条(范围输入字段)
- 邮箱
- 日期
自定义的 JavaScript 字段可以实现:
- 取色器(colorpicker)
- 日历
- 其他功能
无论表单字段看起来多么复杂,仍然只需要关注两件事
- 字段名称————查看源代码中的
name
属性获得 - 字段值————有时会比较复杂(由JavaScript生成)
如果遇到了一个看起来很复杂的 POST 表单,并且想查看浏览器向服务器传递了哪些参数,可以用浏览器检查器或者开发者工具查看
Network -> ALL -> processing.php -> Headers -> Form Data
可以看到刚刚提交的参数
提交文件和图片
在网页 http://pythonscraping.com/files/form2.html 上由一个文件上传表单
网页源码如下
1 | <form action="../pages/files/processing2.php" method="post" enctype="multipart/form-data"> |
<input>
标签里有一个file
属性,其他的大同小异,用 requests 库处理起来也非常相似
1 | files = { |
可以在控制台看到输出结果
1 | uploads/pic.png |
处理登录和cookie
大多数现代网站都用 cookie 跟踪用户是否已经登录的状态信息
一旦网站验证了你的登录凭据,就会在你的浏览器上将其保存为一个 cookie,里面通常包含一个由服务器生成的令牌、登录有效时限和状态跟踪信息
网站会把这个 cookie 当作一种验证证据,在你浏览网站的每个页面时都出示给服务器
该网站 http://pythonscraping.com/pages/cookies/login.html 上有一个简单的登录表单(填写密码时必须是 ‘password’)
打开浏览器检查器可以找到如下两条信息
1 | <input type="text" name="username"> |
用 requests 库跟踪 cookie
1 | params = { |
可以看到如下打印信息
1 | Cookies: {'loggedin': '1', 'username': 'Nopech'} |
对于简单的访问,这样处理没有问题,但是如果网站比较复杂,经常暗自调整 cookie,或者你不想使用 cookie,requests 库的 session 函数就可以完美解决这个问题
1 | import requests |
打印信息与上个例子相同
该例中,会话(session)对象会持续跟踪会话信息,包括 cookie, header,甚至是 HTTP 协议的信息,比如 HTTPAdapter
HTTP基本接入认证
在发明 cookie 之前,处理网站登录的一种常用方法就是用 HTTP 基本接入认证(HTTP basic access authentication)
在一些安全性较高的网站或公司网站、以及一些 API 上,可能会经常遇见
在 http://pythonscraping.com/pages/auth/login.php 网页上就是该认证方法创建的页面
用户名任意填写,密码必须是 ‘password’
requests 库下有一个 auth 模块,专门处理 HTTP 认证
1 | import requests |
输出结果如下
1 | # <h2>Welcome to the Website!</h2> |
这看着像普通的 POST 请求,但是有一个 HTTPBasicAuth 对象作为 auth 参数传递到了请求中
显示的结果将是用户名和密码验证成功的页面
如果验证失败,就是一个拒绝接入页面
抓取 JavaScript
客户端脚本语言是在浏览器上运行的
客户端语言成功的前提是浏览器能够正确地解释和执行这类语言,这也是在浏览器上禁用 JavaScript 非常容易的语音
目前为止,JavaScript 是 Web 上最常用也是支持者最多的客户端脚本语言,它可以收集用户跟踪数据,不需要重载页面直接提交表单,在页面中嵌入多媒体文件,甚至运行在线游戏
用 Selenium 执行 JavaScript
Selenium 是一个强大的网页抓取工具,最初是为网站自动化测试开发的
它还被用于获取精确的网站快照,可以让浏览器自动加载网站,获取需要的数据,甚至对页面截屏,或者判断网站上是否发生了某些操作
Selenium 自身不带浏览器,需要与第三方浏览器集成才能运行
在浏览器上运行 Selenium,会打开一个窗口,进入网站,然后执行代码中设置的动作
当然,有一个叫 PhantomJS 的工具可以代替真实的浏览器
PhantomJS 是一个无头(headless)浏览器,会把网站加载到内存并执行页面上的 JavaScript,但不会向用户展示网页的图形界面
安装 Selenium
1 | pip install selenium |
PhantomJS 可以从它的官方网站下载,因为其是一个功能完善的浏览器,并非 Python 库
Selenium 库是一个在 WebDriver 对象上调用的 API
WebDriver 有点像可以加载网站的浏览器,但是它可以像 BeautifulSoup 对象一样用来查找页面元素,与页面上的元素交互(发送文本、点击等),以及执行其他动作来运行网络爬虫
下面的代码可以获得测试页面 http://pythonscraping.com/pages/javascript/ajaxDemo.html 上的内容
1 | from selenium import webdriver |
顺利执行就会看到类似如下的输出结果
1 | Here is some important text you want to retrieve! |
当然,也可能会报浏览器版本不匹配等错误,如:
1 | selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 86 |
这时,可以通过 webdriver-manager
解决
1 | from selenium import webdriver |
回到刚刚的例子,我们可以用其他的选择器来获取标签或元素:
driver.find_element_by_css_selector('#content')
driver.find_element_by_tag_name('div')
driver.find_element_by_class_name('class-name')
- …
如果想选择多个元素,大部分选择器可以用 elements
来返回一个列表
driver.find_elements_by_css_selector('#content')
driver.find_elements_by_tag_name('div')
- …
另外,如果还是想用 BeautifulSoup 来解析网页内容,可以用 WebDriver 的 page_source 函数返回页面的源代码字符串
1 | page = driver.page_source |
再次回到刚刚的例子,对于time.sleep()
函数,这并不是无用的,因为很多网站页面加载时间是不确定的,具体依赖服务器某一毫秒的负载情况,以及多变的网速
不过用time.sleep()
函数效率不够高,可能也会失效,这时,就可以换种方法,让 Selenium 不断检查某元素是否存在,以此确定页面是否加载完毕
1 | from selenium import webdriver |
该程序检查 ID 为 loadedButton
的按钮是否存在,以此判断页面是不是已经完全加载
程序导入了一些新模块,WebDriverWait
与 expected_conditions
组合起来构成了 Selenium 的隐式等待(imlicit wait)
隐式等待与显示等待的不同之处,在于隐式等待是等 DOM 中某个状态发生后再继续运行代码,而显示等待明确设置了等待时间
隐式等待中,DOM 触发的状态是用 expected_conditions
定义的,在 Selenium 库里元素被触发的期望条件有很多:
- 弹出一个提示框
- 一个元素被选中
- 页面的标题改变了,或者文本显示在页面上或某个元素里
- 一个元素对 DOM 可见,或者一个元素从 DOM 中消失了
- …
大多数期望条件在使用前都需要你指定等待的目标元素,元素用定位器指定
定位器是一种抽象的查询语言,用 By
对象表示,可以用于不同场合,包括创建选择器,如下两个例子
1 | EC.presence_of_element_located( |
定位器通过 By
对象进行选择有很多种策略,如:
ID
CLASS_NAME
CSS_SELECTOR
LINK_TEXT
XPATH
- …
处理重定向
客户端重定向是在服务器将页面内容发送到浏览器之前,由浏览器执行 JavaScript 完成的页面跳转,而不是服务器完成的跳转
在进行网页抓取时,客户端重定向与服务器端重定向的差异非常明显
Selenium 可以执行这种 JavaScript 重定向,但是需要学会怎么判断一个页面已经完成重定向,接下来,以网站 http://pythonscraping.com/pages/javascript/redirectDemo1.html 来举例
首先从页面开始加载时就监视 DOM 中的一个元素,然后重复调用这个元素,直到 Selenium 抛出一个 `` 异常,即元素不在页面的 DOM 里了,这说明网站已经跳转
1 | import time |
这个程序每0.5秒检查一次网页,看看 html 标签还在不在,时限为10秒
除此之外,还可以编写一个类似的循环来检查当前页面的 URL,直到 URL 发生改变,或者匹配到你寻找的特定 URL
等待元素的出现和消失时 Selenium 中一个常见的任务,这里提供一个15秒的时限和一个 XPath 选择器,完成同样的内容
1 | from selenium import webdriver |