Post

Python爬虫实战

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反爬虫机制,最好每次发送请求都添加请求头。

请求头的参数如下:

  1. Accept:text/html,image/ *(浏览器可以接收的文件类型)。
  2. Accept-Charset:ISO-8859-1(浏览器可以接收的编码类型)。
  3. Accept-Encoding:gzip,compress(浏览器可以接收的压缩编码类型)。
  4. Accept-Language:en-us,zh-cn(浏览器可以接收的语言和国家类型)。
  5. Host:请求的主机地址和端口。
  6. If-Modified-Since:Tue, 11 Jul 2000 18:23:51 GMT(某个页面的缓存时间)。
  7. Referer:请求来自于哪个页面的URL。
  8. User-Agent:Mozilla/4.0(compatible, MSIE 5.5,Windows NT 5.0,浏览器相关信息)。
  9. Cookie:浏览器暂存服务器发送的信息。
  10. Connection:close(1.0)/Keep-Alive(1.1)(HTTP请求版本的特点)。
  11. Date:Tue, 11 Jul 2000 18:23:51 GMT(请求网站的时间)。

Ajax

Ajax(Asynchronous JavaScript and XML)不是一种新的编程语言,而是一种用于创建更好、更快以及交互性更强的Web应用程序的技术。使用JavaScript向服务器提出请求并处理响应而不阻塞用户,核心对象是XMLHTTPRequest(XHR)。通过这个对象,JavaScript可在不重载页面的情况下与Web服务器交换数据,即在不需要刷新页面的情况下就可以产生局部刷新的效果。Ajax动态数据抓取一般使用chromium。

文件编码

一些常见编码:

  1. ISO8859-1:单字节,范围0-255,用于英文字符
  2. GB2312:1981年中国制定,表示6000多个汉字
  3. GBK:在GB2312基础上扩充,表示2万多汉字
  4. GB18030:在GBK基础上,进一步加入少数民族的字符,表示7万多个汉字

使用request库处理文件编码:

  • res.encoding:查看网页返回的字符集类型
  • res.apparent_encoding:自动判断字符集类型
  • response.content.decode(‘utf-8’):手动对爬取的内容进行解码

网络请求分析

一般分析网站最主要的是找到数据的来源,确定数据来源就能确定数据生成的具体方法。

总结归纳分析网站的步骤如下:

  1. 找出数据来源,大部分数据来源于Doc、XHR和JS标签。
  2. 找到数据所在的请求,分析其请求链接、请求方式和请求参数。
  3. 查找并确定请求参数来源。有时候某些请求参数是通过另外的请求生成的,比如请求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是服务器返回的结果,两者之间对不同类型的响应结果有不同的显示方式:

  1. 如果返回的结果是图片,那么Preview表示可显示图片内容,Response表示无法显示。
  2. 如果返回的是HTML或JSON,那么两者皆能显示,但在格式上可能会存在细微的差异。

Headers标签

划分为以下4部分:

  1. General:记录请求链接、请求方式和请求状态码。
  2. 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字段的内容分别缓存不同的版本。
  3. 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类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
  4. Query String Parameters:请求参数。主要是将参数按照一定的形式(GET和POST)传递给服务器,服务器通过接收其参数进行相应的响应,这是客户端和服务端进行数据交互的主要方式之一。

Headers标签的内容看起来很多,但在实际使用过程中,爬虫开发人员只需关心请求链接、请求方式、请求头和请求参数的内容即可。

Urllib库

Urllib是Python自带的标准库,无须安装,通常用于爬虫开发、API(应用程序编程接口)数据获取和测试。

Urllib是一个收集几个模块来使用URL的软件包,大致具备以下功能:

  1. urllib.request:用于打开和读取URL
  2. urllib.error:包含提出的例外urllib.request
  3. urllib.parse:用于解析URL
  4. 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提供以下方法获取响应内容。

  1. r.status_code:响应状态码。
  2. r.raw:原始响应体,使用r.raw.read()读取。
  3. r.content:字节方式的响应体,需要进行解码。
  4. r.text:字符串方式的响应体,会自动根据响应头部的字符编码进行解码。
  5. r.headers:以字典对象存储服务器响应头
  6. r.json():Requests中内置的JSON解码器。
  7. r.raise_for_status():请求失败(非200响应),抛出异常。
  8. r.url:获取请求链接。
  9. r.cookies:获取请求后的cookies。
  10. 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_ENCODINGDEFAULT_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

通过标签值实现定位,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'))

clicksubmit在某些情况下可以相互使用,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)。三种方法在不同场景下有不同优势,取长补短,应根据实际情况选择合理的清洗方法,三种方法同时出现在一个项目也是常见的事情。

This post is licensed under CC BY 4.0 by the author.