Python爬虫实战
HTTP 和 HTTPS
HTTP(Hyper Text Transfer Protocol,超文本传输协议)是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通过使用 Web 浏览器、网络爬虫或者其他工具,客户端发起一个到服务器上指定端口(默认端口为 80)的 HTTP 请求,这个客户端叫用户代理(User Agent)。响应的服务器上存储着资源,比如 HTML 文件和图像,这个服务器为源服务器(OriginServer),在用户代理和服务器中间可能存在多个中间层,比如代理、网关。
HTTP 协议传输的数据都是未加密的,也就是明文的数据,因此使用 HTTP 协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了 SSL(Secure Sockets Layer)协议用于对 HTTP 协议传输的数据进行加密,从而诞生了 HTTPS。
HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer,可以理解为 HTTP+SSL/TLS)在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。
请求头
请求头描述客户端向服务器发送请求时使用的协议类型、所使用的编码以及发送内容的长度等。客户端(浏览器)通过输入 URL 后确定等于做了一次向服务器的请求动作,在这个请求里面带有请求参数,请求头在网络爬虫中的作用是相当重要的一部分。检测请求头是常见的反爬虫策略,因为服务器会对请求头做一次检测来判断这次请求是人为的还是非人为的。
为了形成一个良好的代码编写规范,无论网站是否做 Headers 反爬虫机制,最好每次发送请求都添加请求头。
请求头的参数如下:
- Accept:text/html,image/ *(浏览器可以接收的文件类型)。
- Accept-Charset:ISO-8859-1(浏览器可以接收的编码类型)。
- Accept-Encoding:gzip,compress(浏览器可以接收的压缩编码类型)。
- Accept-Language:en-us,zh-cn(浏览器可以接收的语言和国家类型)。
- Host:请求的主机地址和端口。
- If-Modified-Since:Tue, 11 Jul 2000 18:23:51 GMT(某个页面的缓存时间)。
- Referer:请求来自于哪个页面的 URL。
- User-Agent:Mozilla/4.0(compatible, MSIE 5.5,Windows NT 5.0,浏览器相关信息)。
- Cookie:浏览器暂存服务器发送的信息。
- Connection:close(1.0)/Keep-Alive(1.1)(HTTP 请求版本的特点)。
- Date:Tue, 11 Jul 2000 18:23:51 GMT(请求网站的时间)。
Ajax
Ajax(Asynchronous JavaScript and XML)不是一种新的编程语言,而是一种用于创建更好、更快以及交互性更强的 Web 应用程序的技术。使用 JavaScript 向服务器提出请求并处理响应而不阻塞用户,核心对象是 XMLHTTPRequest(XHR)。通过这个对象,JavaScript 可在不重载页面的情况下与 Web 服务器交换数据,即在不需要刷新页面的情况下就可以产生局部刷新的效果。Ajax 动态数据抓取一般使用 chromium。
文件编码
一些常见编码:
- ISO8859-1:单字节,范围 0-255,用于英文字符
- GB2312:1981 年中国制定,表示 6000 多个汉字
- GBK:在 GB2312 基础上扩充,表示 2 万多汉字
- GB18030:在 GBK 基础上,进一步加入少数民族的字符,表示 7 万多个汉字
使用 request 库处理文件编码:
res.encoding
:查看网页返回的字符集类型res.apparent_encoding
:自动判断字符集类型response.content.decode(‘utf-8’)
:手动对爬取的内容进行解码
网络请求分析
一般分析网站最主要的是找到数据的来源,确定数据来源就能确定数据生成的具体方法。
总结归纳分析网站的步骤如下:
- 找出数据来源,大部分数据来源于 Doc、XHR 和 JS 标签。
- 找到数据所在的请求,分析其请求链接、请求方式和请求参数。
- 查找并确定请求参数来源。有时候某些请求参数是通过另外的请求生成的,比如请求 A 的参数 id 是通过请求 B 所生成的,那么要获取请求 A 的数据,就要先获取请求 B 的数据作为 A 的请求参数。
浏览器开发者工具
Chrome 开发者工具的界面共有 9 个标签页,分别是:Elements、Console、Sources、Network、Performance、Memory、Application、Security 和 Audits。
Chrome 开发者工具以 Web 调试为主,如果用于爬虫分析,熟练掌握 Elements 和 Network 标签就能满足大部分的爬虫需求。其中,Network 是核心部分。
在 Network 标签中可以看到页面向服务器请求的信息、请求的大小以及加载请求花费的时间。
Network 标签主要包括以下 5 个区域:
- Controls:控制 Network 的外观和功能。
- Filters:控制 Requests Table 具体显示哪些内容。
- Overview:显示获取到请求的时间轴信息,主要是对每个请求信息在服务器的响应时间进行记录。
- Requests Table:按前后顺序显示所有捕捉的请求信息,单击请求信息可以查看该详细信息。
- Summary:显示总的请求数、数据传输量、加载时间信息。
5 个区域中,Requests Table 是核心部分,主要作用是记录每个请求信息。对于每条请求信息,可以单击查看该请求的详细信息:
- Headers:该请求的 HTTP 头信息。
- Preview:根据所选择的请求类型(JSON、图片、文本)显示相应的预览。
- Response:显示 HTTP 的 Response 信息。
- Cookies:显示 HTTP 的 Request 和 Response 过程中的 Cookies 信息。
- Timing:显示请求在整个生命周期中各部分花费的时间。
Preview 和 Response 是服务器返回的结果,两者之间对不同类型的响应结果有不同的显示方式:
- 如果返回的结果是图片,那么 Preview 表示可显示图片内容,Response 表示无法显示。
- 如果返回的是 HTML 或 JSON,那么两者皆能显示,但在格式上可能会存在细微的差异。
Headers 标签
划分为以下 4 部分:
- General:记录请求链接、请求方式和请求状态码。
- Response Headers:服务器端的响应头,其参数说明如下。
- Cache-Control:指定缓存机制,优先级大于 Last-Modified。
- Connection:包含很多标签列表,其中最常见的是 Keep-Alive 和 Close,分别用于向服务器请求保持 TCP 连接和断开 TCP 连接。
- Content-Encoding:服务器通过这个头告诉浏览器数据的压缩格式。
- Content-Length:服务器通过这个头告诉浏览器回送数据的长度。
- Content-Type:服务器通过这个头告诉浏览器回送数据的类型。
- Date:当前时间值。
- Keep-Alive:在 Connection 为 Keep-Alive 时,该字段才有用,用来说明服务器估计保留连接的时间和允许后续几个请求复用这个保持着的连接。
- Server:服务器通过这个头告诉浏览器服务器的类型。
- Vary:明确告知缓存服务器按照 Accept-Encoding 字段的内容分别缓存不同的版本。
- Request Headers:用户的请求头。其参数说明如下。
- Accept:告诉服务器客户端支持的数据类型。
- Accept-Encoding:告诉服务器客户端支持的数据压缩格式。
- Accept-Charset:可接受的内容编码 UTF-8。
- Cache-Control:缓存控制,服务器控制浏览器要不要缓存数据。
- Connection:处理完这次请求后,是断开连接还是保持连接。
- Cookie:客户可通过 Cookie 向服务器发送数据,让服务器识别不同的客户端。
- Host:访问的主机名。
- Referer:包含一个 URL,用户从该 URL 代表的页面出发访问当前请求的页面,当浏览器向 Web 服务器发送请求的时候,一般会带上 Referer,告诉服务器请求是从哪个页面 URL 过来的,服务器借此可以获得一些信息用于处理。
- User-Agent:中文名为用户代理,简称 UA,是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
- Query String Parameters:请求参数。主要是将参数按照一定的形式(GET 和 POST)传递给服务器,服务器通过接收其参数进行相应的响应,这是客户端和服务端进行数据交互的主要方式之一。
Headers 标签的内容看起来很多,但在实际使用过程中,爬虫开发人员只需关心请求链接、请求方式、请求头和请求参数的内容即可。
Urllib 库
Urllib 是 Python 自带的标准库,无须安装,通常用于爬虫开发、API(应用程序编程接口)数据获取和测试。
Urllib 是一个收集几个模块来使用 URL 的软件包,大致具备以下功能:
- urllib.request:用于打开和读取 URL
- urllib.error:包含提出的例外 urllib.request
- urllib.parse:用于解析 URL
- urllib.robotparser:用于解析 robots.txt 文件
发送请求:urllib.request.urlopen()
Urllib 是用于访问 URL(请求链接)的唯一方法,返回值为网站的响应内容(HTTPResponse 类型)。
1
2
urllib.request.urlopen(url, data=None, [timeout, ]*,
cafile=None, capath=None, cadefault=False, context=None)
在实际使用中,常用的参数有 url、data 和 timeout。
data 默认值为 None, Urllib 判断参数 data 是否为 None 从而区分请求方式。若参数 data 为 None,则代表请求方式为 GET;反之请求方式为 POST,发送 POST 请求。参数 data 以字典形式存储数据,并将参数 data 由字典类型转换成字节类型才能完成 POST 请求。
timeout 为超时设置,指定阻塞操作(请求时间)的超时(如果未指定,就使用全局默认超时设置),如果网站没有在超时设置的时间内返回响应数据,就会提示请求失败的错误信息。
1
2
3
4
5
6
7
import urillib.request
response = urllib.request.urlopen('https://movie.douban.com', None, 2)
html = response.read().decode('utf-8')
f = open('html.txt', 'w', encoding='utf-8')
f.write(html)
f.close()
复杂请求:urllib.request.Request()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib.request
url = 'https://movie.douban.com/'
# 可以在chrome的network选项卡中找到请求标头的内容
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
'Connection': 'keep-alive',
'Host': 'movie.douban.com'}
req = urllib.request.Request(url, headers=headers)
html = urllib.request.urlopen(req).read().decode('utf-8')
with open('./spider/urllib101.text', 'w', encoding='utf-8') as f:
f.write(html)
使用代理 IP
以本机先访问代理 IP,再通过代理 IP 地址访问互联网,这样网站(服务器)接收到的访问 IP 就是代理 IP 地址。
Urllib 提供了urllib.request.ProxyHandler()
方法可动态设置代理 IP 池,代理 IP 主要以字典格式写入方法。完成代理 IP 设置后,将设置好的代理 IP 写入urllib.request.build_opener()
方法,生成对象 opener,然后通过 opener 的open()
方法向网站(服务器)发送请求。
使用 Cookies
Cookies 主要用于获取用户登录信息,比如,通过提交数据实现用户登录之后,会生成带有登录状态的 Cookies,这时可以将 Cookies 保存在本地文件中,下次程序运行的时候,可以直接读取 Cookies 文件来实现用户登录。特别对于一些复杂的登录,如验证码、手机短信验证登录这类网站,使用 Cookies 能简单解决重复登录的问题。
Urllib 提供HTTPCookieProcessor()
对 Cookies 操作,但 Cookies 的读写是由MozillaCookieJar()
完成的。
1
2
3
4
5
6
7
8
9
10
11
12
import urllib.request
from http import cookiejar
filename = './spider/cookie.text'
# 保存cookie
cookie = cookiejar.MozillaCookieJar(filename)
# 创建cookie处理器
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://baidu.com')
cookie.save()
1
2
3
4
5
6
7
8
9
10
11
12
import urllib.request
from http import cookiejar
filename = './spider/cookie.text'
cookie = cookiejar.MozillaCookieJar(filename)
# load cookie
cookie.load(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://baidu.com/')
print(cookie)
证书验证
CA 证书也叫 SSL 证书,是数字证书的一种,类似于驾驶证、护照和营业执照的电子副本。因为配置在服务器上,也称为 SSL 服务器证书。
SSL 证书就是遵守 SSL 协议,由受信任的数字证书机构颁发 CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
SSL 证书在客户端浏览器和 Web 服务器之间建立一条 SSL 安全通道(Secure Socket Layer, SSL),安全协议是由 Netscape Communication 公司设计开发的。该安全协议主要用来提供对用户和服务器的认证,对传送的数据进行加密和隐藏,确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准。
一些特殊的网站会使用自己的证书,如 12306 首页提示下载安装根证书,这是为了确保网站的数据在传输过程中的安全性。urllib.request.urlopen()
带有 cafile、capath 和 cadefault 参数,可以用于设置用户的 CA 证书。
遇到这类验证证书的网站,最简单而暴力的方法是直接关闭证书验证,可以在代码中引入 SSL 模块,设置关闭证书验证即可。
1
2
3
4
5
6
7
8
import urllib.request
import ssl
# close ssl varification
ssl._create_default_https_context = ssl._create_unverified_context
url = 'https://kyfw.12306.cn/otn/leftTicket/init'
response = urllib.request.urlopen(url)
print(response.getcode())
POST 请求的数据处理
urllib.request.urlopen()
方法是不区分请求方式的,识别请求方式主要通过参数 data 是否为 None。如果向服务器发送 POST 请求,那么参数 data 需要使用urllib.parse
对参数内容进行处理。
Urllib 在请求访问服务器的时候,如果发生数据传递,就需要对内容进行编码处理,将包含 str 或 bytes 对象的两个元素元组序列转换为百分比编码的 ASCII 文本字符串。如果字符串要用作 POST,那么它应该被编码为字节,否则会导致 TypeError 错误。
1
2
3
4
5
6
7
8
9
import urllib.request
import urllib.parse
url = 'https://movie.douban.com'
data = {
'value':'true',
}
# 将数据转换成字节的数据类型,并设置字节的编码格式
data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.urlopen(url, data=data)
同样也可使用quote()
和unquote()
对数据进行编码处理,作用是解决请求参数中含有中文内容的问题。
Requests 库
与 Urllib 对比,Requests 不仅具备 Urllib 的全部功能;在开发使用上,语法简单易懂,完全符合 Python 优雅、简洁的特性。
请求方式
GET 请求有两种形式,分别是不带参数和带参数。一般网址末端(域名)带有“?”,就说明该 URL 是带有请求参数的。
如下所示:wd 是参数名,参数名由网站(服务器)规定;python 是参数值,可由用户自行设置;如果一个 URL 有多个参数,参数之间用&
连接。
1
https://baidu.com/s?wd=python
Requests 实现 GET 请求,对于带参数的 URL 有两种请求方式:r1 和 r2。在实际开发中建议使用第一种,因为代码简洁,如果参数是动态变化的,那么可使用字符串格式化对 URL 动态设置。
Requests 实现 POST 请求,需设置请求参数 data,格式可以为字典、元组、列表和 JSON。
Requests 的 GET 和 POST 方法的请求参数分别是 params 和 data,别混淆。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
r1 = requests.get('https://www.baidu.com/s?wd=python')
url = 'https://www.baidu.com/s'
params = {'wd': 'python'}
r2 = requests.get(url, params=params)
# 可以观察返回的URL是什么样, 由于百度设置了验证措施,所以两种方式返回的值会有所区别。
print(r1.url)
# 用requests实现post请求
import json
data = {
'key1': 'value1',
'key2': 'value2'}
data = json.dumps(data)
r3 = requests.post('https://www.baidu.com/', data=data)
print(r3.text)
复杂的请求方式
复杂的请求方式通常有请求头、代理 IP、证书验证和 Cookies 等功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
# 1. 添加请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
'Connection': 'keep-alive'}
requests.get("https://www.baidu.com/", headers=headers)
# 2. 使用代理IP
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
requests.get("https://www.baidu.com/", proxies=proxies)
# 3. 证书验证 一般设置为关闭即可,若需要配置证书文件,可修改verify参数
url = 'https://kyfw.12306.cn/otn/leftTicket/init'
# 关闭证书验证
r = requests.get(url, verify=False)
print(r.status_code)
# 配置证书验证
r = requests.get(url, verify=True)
r = requests.get(url, verify='/path/to/certfile')
# 4. 超时设置
requests.get("https://www.baidu.com/", timeout=0.001)
# 5. 使用cookies
temp_cookies = "sspai_cross_token=logout; _ga=GA1.2.108396303.1689636096; _clck=1859y1o|2|fdf|0|1293; Hm_lvt_92174dab8163cf598817a93d11d5c588=1689636096,1689731302; _ga_8K169TXENS=GS1.1.1689731301.2.1.1689731334.27.0.0"
cookies_dict = {}
for i in temp_cookies.split(';'):
value = i.split('=')
cookies_dict[value[0]] = value[1]
r = requests.get(url, cookies=cookies)
print(r.text)
下载与上传
对文件所在 URL 地址发送请求(大多数是 GET 请求方式),服务器将文件内容作为响应内容,然后将得到的内容以字节流格式写入自定义文件,这样就能实现文件下载。
1
2
3
4
5
6
import requests
url = 'https://www.python.org/static/img/python-logo.png'
r = requests.get(url)
with open('./spider/logo.png', 'wb') as f:
f.write(r.content)
文件上传是将本地文件以字节流的方式上传到服务器,再由服务器接收上传内容,并做出相应的响应。文件上传存在一定的难度,其难点在于服务器接收规则不同,不同的网站接收的数据格式和数据内容会不一致。
获取响应内容
当向网站(服务器)发送请求时,网站会返回相应的响应(response)对象,包含服务器响应的信息。Requests 提供以下方法获取响应内容。
r.status_code
:响应状态码。r.raw
:原始响应体,使用r.raw.read()
读取。r.content
:字节方式的响应体,需要进行解码。r.text
:字符串方式的响应体,会自动根据响应头部的字符编码进行解码。r.headers
:以字典对象存储服务器响应头r.json()
:Requests 中内置的 JSON 解码器。r.raise_for_status()
:请求失败(非 200 响应),抛出异常。r.url
:获取请求链接。r.cookies
:获取请求后的 cookies。r.encoding
:获取编码格式。
Requests-Cache 爬虫缓存
当 Requests 重复向同一个 URL 发送请求的时候,Requests-Cache 会判断当前请求是否已产生缓存,若已有缓存,则从缓存里读取数据作为响应内容;若没有缓存,则向网站服务器发送请求,并将得到的响应内容写入相应的数据库里。
Requests-Cache 的作用非常重要,它可以减少网络资源重复请求的次数,不仅减轻了本地的网络负载,而且还减少了爬虫对网站服务器的请求次数,这也是解决反爬虫机制的一个重要手段。
安装:pip install requests-cache
Requests-Cache 的缓存机制由install_cache()
方法实现。在实际应用中install_cache()
可以直接使用,无需设置任何参数,因为 Requests-Cache 已对相关的参数设置了默认值,这些默认值基本能满足日常的开发需求。
为了验证 Requests-Cache,首先使用 Flask 框架创建一个简单的网站系统:
1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask
app = Flask(__name__)
# set route or url
@app.route('/')
def hello_world():
return 'hello world! this is Flask.'
if __name__ == '__main__':
app.run()
浏览器每次成功访问网站,都会在网站后台出现相关的请求信息。使用 Requests-Cache 对网站进行两次访问,查看网站后台请求信息的出现次数。如果请求信息只出现一次,说明爬虫缓存正常使用,反之则说明 Requests-Cache 无法生成缓存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import requests_cache
requests_cache.install_cache()
# clear the existed cache
requests_cache.clear()
url = 'http://127.0.0.1:5000/'
session = requests.session()
for t in range(2):
r = session.get(url)
# from cache如果输出是真,则说明缓存已生成,且当前响应来自缓存
print(r.from_cache)
运行上述代码,程序会依次输出 False 和 True, False 代表第一次访问还没有生成相关的缓存;True 代表第二次访问就已有相关的缓存数据。同时代码所在的文件路径中会生成 cache.sqlite 文件,这是 sqlite 数据库文件,用于存储缓存信息。
如何判断这次请求是否已有缓存,每个请求之间应如何合理地设置延时等待?Requests-Cache 可以自定义钩子函数,通过函数去合理判断是否设置延时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
import requests_cache
# 装饰器的原理没太懂,还要在实践中继续消化
def make_throttle_hook(delay=1.0):
def hook(response, *args, **kwargs):
# 如果没有缓存,则添加延迟
if not getattr(response, 'from_cache', False):
print('delayTime')
time.sleep(delay)
return response
return hook
if __name__=='__main__':
requests_cache.install_cache()
requests_cache.clear()
s = requests_cache.CacheSession()
s.hooks = {'response': make_throttle_hook(2)}
s.get('http://127.0.0.1:5000')
s.get('http://127.0.0.1:5000')
Requests-Cache 支持 4 种不同的存储机制:memory(将缓存以字典的形式保存在内存中,程序运行完毕,缓存也随之销毁)、sqlite(默认)、redis 和 mongoDB。设置不同的存储机制只需对install_cache()
方法的参数 backend 进行设置即可。
如果选择 redis 或 mongoDB 作为存储介质,还需要分别安装 redis 模块或 pymongo 模块,这两个模块均可通过 pip 指令安装,同时也要保证本地计算机已安装 redis 或 mongoDB 数据库。
Requests-HTML
Requests-HTML 是在 Requests 的基础上进一步封装,除了包含 Requests 的所有功能之外,还新增了数据清洗和 Ajax 数据动态渲染。
- 数据清洗是由 lxml 和 PyQuery 模块实现,这两个模块分别支持 XPath Selectors 和 CSS Selectors 定位,通过 XPath 或 CSS 定位,可以精准地提取网页里的数据。
- Ajax 数据动态渲染是将网页的动态数据加载到网页上再抓取。网页数据可以使用 Ajax 向服务器发送 HTTP 请求,再由 JavaScript 完成数据渲染,如果直接向网页的 URL 地址发送 HTTP 请求,并且网页的部分数据是来自 Ajax,那么,得到的网页信息就会有所缺失。而 Requests-HTML 可以将 Ajax 动态数据加载到网页信息,无需开发者分析 Ajax 的请求信息。
安装:pip install requests-html
请求方式
Requests-HTML 向网站发送请求的方法是来自 Requests 模块,但是 Requests-HTML 只能使用 Requests 的 Session 模式,该模式是将请求会话实现持久化,使这个请求保持连接状态。Session 模式好比我们在打电话的时候,只要双方没有挂断电话,就会一直保持一种会话状态。
1
2
3
4
5
6
7
8
9
10
from requests_html import HTMLSession
# 创建会话
session = HTMLSession()
url = 'https://movie.douban.com'
# 发送GET请求
r = session.get(url)
# 发送POST请求
r = session.post(url, data={})
print(r.html)
Requests-HTML 在请求过程中还做了优化处理,如果没有设置请求头,Requests-HTML 就会默认使用源码里所定义的请求头以及编码格式。在 Python 的安装目录下打开 Requests-HTML 的源码文件(\Lib\site-packages\requests_html.py
),定义了属性DEFAULT_ENCODING
和DEFAULT_USER_AGENT
,分别对应编码格式和 HTTP 的请求头。
数据清洗
1
2
3
4
5
6
7
8
9
from requests_html import HTMLSession
session = HTMLSession()
url = 'https://movie.douban.com'
r = session.get(url)
print(r.html)
print(r.html.links)
print(r.html.absolute_links)
print(r.text)
print(r.html.text)
上述代码只是提取了网站的基本信息,如果想要精准地提取某个数据,可以使用find()
、xpath()
、search()
和search_all()
方法实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
find(selector, containing, clean, first, _encoding)
#selector:使用css selector定位网页元素。
#containing:字符串类型,默认值为None,通过特定文本查找网页元素。
#clean:是否清除HTML的<script>和<style>标签,默认值为False。
#first:是否只查找第一个网页元素,默认值为即查找全部元素。
#_encoding:设置编码格式,默认值为None。
xpath(selector, clean, first, _encoding)
#selector:使用XPath selector定位网页元素。
#其他参数同上
search(template)
#template:通过元素内容查找第一个元素
search_all(template)
#template:通过元素内容查找全部元素
Python 爬虫实战
提取关键数据
首先通过浏览器定位需要提取的信息的位置,我们可以发现标题在网页中的源代码如下。
<span property="v:itemreviewed">西游记之大圣归来</span>
了解了标题的源代码后,接下来可以使用正则表达式从网页源代码中提取标题数据。
1
2
3
4
5
6
7
8
9
10
11
12
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
import requests
url = 'https://movie.douban.com/subject/26277313/'
res = requests.get(url,headers = headers)
page = res.content.decode()
import re
title = re.search(r'<span property="v:itemreviewed">(.*?)</span>',page,re.S)
print(title.group(1))
运行结果如下。
西游记之大圣归来
万能数据提取表达式(.*?)
,加?是为了打开非贪婪匹配模式。
对爬取的数据进行保存
1
2
3
4
5
6
7
8
9
10
11
12
13
import csv
# 文件保存路径
filePath = 'D://top_weibo.csv'
with open(filePath, 'w') as csvfile:
head = ['地址','标题'] # CSV文件标题
writer = csv.writer(csvfile)
writer.writerow(head) #写入首行数据
for line in range(len(links)):
row = [] # 数据行
row.append(links[line])
row.append(titles[line])
writer.writerow(row) # 写入一行数据
Selenium
Selenium 是一个用于网站应用程序自动化的工具。它可以直接运行在浏览器中,就像真正的用户在操作一样。
安装:pip install selenium
下载对应版本的Chrome Driver,并保存到对应 Python 虚拟环境的安装目录之下。
1
2
3
4
5
6
7
8
from selenium import webdriver
url = 'https://www.baidu.com'
# 实例化webdriver类,并将浏览器设定为chrome
browser = webdriver.Chrome()
browser.get(url)
find_element 和 find_elements
定位网页元素主要通过元素的属性值或者元素在 HTML 里的路径位置,定位方式一共有以下 8 种。
其中find_element
前缀只能匹配第一个元素,find_elements
前缀则可以匹配多个元素。
注意:新版本的 selenium 不再支持老版本的查找语法,需要将代码进行修改
find_element_by_id('inp-query')
修改为 **find_element(By.ID, 'inp-query')**
同时需要在开头导入相应的库函数**from selenium.webdriver.common.by import By**
By:设置元素定位方式。定位方式共 8 种,分别是 ID 、XPATH、LINK_TEXT、PARTIAL_LINK_TEXT、NAME、TAG_NAME、CLASS_NAME、CSS_SELECTOR。
by_id()和 by_name()
通过属性 id 和 name 的属性值来实现定位,如果被定位的元素不存在属性 id 或 name,则无法使用这种定位方式。通常情况下,一个网页中,元素的 id 或 name 的属性值是唯一的。
1
2
find_element_by_id('inp-query').send_keys('红海行动')
find_element_by_name('search_text').send_keys('红海行动')
by_tag_name()和 by_class_name()
通过标签 tag 类型和属性 class 的属性值实现定位。class 的属性值可以被多个元素使用,同一个元素标签也可以多次使用,使用find_element
定位第一个元素,使用find_elements
定位多个元素。
1
2
find_element_by_tag_name('div').text
find_element_by_class_name('global-nav').text
by_link_text()和 by_partial_link_text()
通过标签值实现定位,partial_link 用于模糊匹配。
1
2
find_element_by_link_text('排行榜').text
find_element_by_partial_link_text()('影音报告').text
by_xpath()和 by_css_selector()
通过元素的路径进行定位,最精准的方式。
xpath 和 css_selector 的语法编写规则各不相同,在 Chrome 的开发者工具里里可以快速获取两者的语法:
1
2
find_element_by_xpath('//*[@id="db-nav-movie"]/div[1]/div/div[1]/a').text
find_element_by_css_selector('#db-nav-movie > div.nav-wrap > div > div.nav-logo > a').text
操控网页元素
操控网页元素在完成上述网页元素定位之后才能执行,Selenium 可以模拟任何操作,比如单击、右击、拖拉、滚动、复制粘贴或者文本输入等等,操作方式可分为三大类:常规操作、鼠标事件操作和键盘事件操作。
常规操作
常规操作包含文本清除、文本输入、单击元素、提交表单、获取元素属性值等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from selenium import webdriver
url = 'https://movie.douban.com'
browser = webdriver.Chrome()
browser.get(url)
# 文本输入:在搜索框中输入红海行动
browser.find_element_by_id('inp-query').send_keys('红海行动')
# 提交表单:提交搜索请求
browser.find_element_by_class('inp-btn').submit()
# 文本清除
browser.find_element_by_id('inp-query').clear()
# 单击元素
browser.find_element_by_class('next-btn').click()
# 获取元素在网页中的坐标位置,格式为{'y': 19, 'x': 498}
loc = browser.find_element_by_id('inp-query').location()
# 获取元素的属性值
attr = browser.find_element_by_id('inp-query').get_attribute('size')
# 判断元素在网页上是否可见
result = browser.find_element_by_id('inp-query').is_displayed()
# 判断元素是否被选中,一般用于checkbox和radio标签
result = browser.find_element_by_id('inp-query').is_selected()
# select标签的选值
from selenium.webdriver.support.select import Select
# 根据下拉框的索引值选取
Select(browser.find_element_by_id('X').select_by_index('2'))
# 根据下拉框的value属性选取
Select(browser.find_element_by_id('X').select_by_index('Python'))
# 根据下拉框的值选取
Select(browser.find_element_by_id('X').select_by_visible_text('Python'))
click
和submit
在某些情况下可以相互使用,submit
只用于表单的提交按钮;click
是强调事件的独立性,可用于任何按钮。
鼠标操作
鼠标事件操作由 Selenium 的 ActionChains 类来实现。
键盘操作
模拟人为按下键盘的某个按键,主要通过 send_keys 方法来实现。
常用功能
设置浏览器的参数
在定义 driver 的时候设置 chrome_options 参数,该参数是一个 Options 类所实例化的对象。其中常用的参数是设置浏览器是否可视化和浏览器的请求头等信息,前者可以加快代码运行速度,后者可以有效地防止网站的反爬虫检测。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
url = 'https://movie.douban.com'
chrome_options = Options()
# 设置浏览器参数。--headless是不显示浏览器
chrome_options.add_argument('--headless')
# 设置lang和user-agent信息,防止反爬虫检测
chrome_options.add_argument('lang=zh.UTF-8')
UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
# 启动浏览器并设置chrome_options参数
driver = webdriver.Chrome(options=chrome_options)
driver.maximize_window()
driver.minimize_window()
driver.get(url)
print(driver.title)
浏览器多窗口切换
在同一个浏览器中切换不同的网页窗口。打开浏览器可以看到,浏览器顶部可以不断添加新的窗口,而 Selenium 可以通过窗口切换来获取不同的网页信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from selenium import webdriver
import time
url = 'https://www.baidu.com'
driver = webdriver.Chrome()
driver.get(url)
# open new window with JavaScript
js = 'window.open("https://www.sogou.com");'
driver.execute_script(js)
current_window = driver.current_window_handle
handles = driver.window_handles
# set delay to see how windows switch
time.sleep(3)
driver.switch_to.window(handles[0])
time.sleep(3)
driver.switch_to.window(handles[1])
延时
Selenium 的执行速度相当快,在 Selenium 执行的过程中往往需要等待网页的响应才能执行下一个步骤,否则程序会抛出异常信息。网页响的应快慢取决于多方面因素,因此在某些操作之间需要设置一个等待时间,让 Selenium 与网页响应尽量达到同步执行,这样才能保证程序的稳健性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from selenium import webdriver
from selenium.webdriver.common.by import By
url = 'https://www.baidu.com'
driver = webdriver.Chrome()
driver.get(url)
driver.implicitly_wait(30)
driver.find_element(By.ID, 'kw').send_keys('Python')
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
condition = expected_conditions.visibility_of_element_located((By.ID, 'kw'))
WebDriverWait(driver=driver, timeout=20, poll_frequency=0.5).until(condition)
隐性等待是在一个设定的时间内检测网页是否加载完成,也就是一般情况下你看到浏览器标签栏那个小圈不再转,才会执行下一步。比如代码中设置 30 秒等待时间,网页只要在 30 秒内完成加载就会自动执行下一步,如果超出 30 秒就会抛出异常。值得注意的是,隐性等待对整个 driver 的周期都起作用,所以只要设置一次即可。
显性等待能够根据判断条件而进行灵活地等待,程序每隔一段时间检测一次,如果检测结果与条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间为止,然后抛出 TimeoutException 异常。
隐性等待和显性等待相比于 time.sleep 这种强制等待更为灵活和智能,可解决各种网络延误的问题,隐性等待和显性等待可以同时使用,但最长的等待时间取决于两者之间的最大数。
Cookies 的使用
Cookies 操作无非就是读取、添加和删除 Cookies。一个网页的 Cookies 可以有多条 Cookie 数据组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get('https://www.youdao.com')
time.sleep(5)
driver.add_cookie({'name': 'Login_User', 'value': 'PassWord'})
all_cookies = driver.get_cookies()
print('all cookies are:', all_cookies)
one_cookie = driver.get_cookie('Login_User')
print('one cookie is:', one_cookie)
driver.delete_cookie('Login_User')
remaining = driver.get_cookies()
print('remaining cookies are:', remaining)
driver.delete_all_cookies()
remaining = driver.get_cookies()
print('remaining cookies are:', remaining)
Appium
Appium 是一个开源、跨平台的测试框架,可以用来测试原生及混合的移动端应用。它允许自动化人员在不同的平台(iOS 和 Android)使用同一套 API 来写自动化脚本,这样大大增加了 iOS 和 Android 的代码复用性。
整个 Appium 分为 Client 和 Server 两部分:Client 封装了 Selenium 客户端类库,为用户提供所有常见的 Selenium 命令以及额外的移动设备控制相关的命令,如多点触控手势和屏幕朝向等;Server 定义了官方协议的扩展,为用户提供了方便的接口来执行各种设备的行为,例如在测试过程中安装/卸载 App 等。
Splash, Mitmproxy, Aiohttp
Splash 动态数据抓取
Splash 是一个带有 API 接口的轻量级浏览器,使用它提供的 API 接口可以简单实现 Ajax 动态数据的抓取。它与 Selenium 所实现的功能都是相同的,只不过实现的过程和原理有所不同。
安装:基于 Docker 应用容器
Mitmproxy 抓包
Mitmproxy 是一个支持 HTTP 和 HTTPS 的抓包程序,实现的功能与 Fiddler 抓包工具相同,只不过它是以控制台的形式操作。
Appium 侧重于手机的自动化操控,对图片、视频、文字内容爬取相对困难,而 Mitmproxy 则补完了 Appium 的缺点,可以说 Appium+Mitmproxy 是手机 App 爬虫开发的利器。
安装:pip install mitmproxy
Aiohttp 高并发抓取
Aiohttp 是 Python 的一个第三方网络编程模块,具有异步并发的特性,它可以开发服务端和客户端,服务端也就是常说的网站服务器;客户端是访问网站的 API 接口,常用于接口测试,也可用于网络爬虫。
安装:pip install aiohttp
验证码识别
对于用户登录设置验证码识别的网站有三种解决方案:
(1)人工识别验证码。将验证码图片下载到本地,然后靠使用者自行识别并将识别内容输入,程序获取输入内容,完成用户登录。其特点是开发简单,适合初学者,但过分依赖人为控制,难以实现批量爬取。
(2)通过 Python 调用 OCR 引擎识别验证码。这是最理想的解决方案,但正常情况下,OCR 准确率较低,需要机器学习不断提高 OCR 准确率,开发成本相对较高。
(3)调用 API 使用第三方平台识别验证码。开发成本较低,有完善的 API 接口,直接调用即可,识别准确率高,但每次识别需收取小额费用。
OCR
OCR(Optical Character Recognition,光学字符识别)是指电子设备检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。
Python 中,支持 ORC 的模块有 pytesser3 和 pyocr,其原理主要是通过模块功能调用 OCR 引擎识别图片,OCR 引擎再将识别的结果返回到程序中。
依赖库安装:pip install pyocr, Pillow
OCR 引擎安装:Tesseract-OCR 是一个免费、开源的 OCR 引擎。
数据清洗
清洗数据有三种常用的方法:字符串操作、正则表达式和第三方模块库(如 BeautifulSoup)。三种方法在不同场景下有不同优势,取长补短,应根据实际情况选择合理的清洗方法,三种方法同时出现在一个项目也是常见的事情。