Python Web Spider Notes

第2章 基本库的使用

2.1 urllib 的使用

urllib s是 Python 内置的 HTTP 请求库。

包含四个模块:

  • request, 模拟请求的发送
  • error, 处理异常
  • parse, 提供 url 的解决方法
  • robotparser, 判断哪些网站可以爬

1 发送请求

urlopen

如:

1
2
3
4
import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))

可以利用 type 方法输出响应类型:

1
print(type(request))

利用 read() 方法可以获得响应的网页内容,status 属性可以得到响应结果的状态码。

urlopen 方法的 API:

1
urllib.request.urlopen(url, date=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
data 参数

使用该参数,其请求方式会变为 POST, 且需要将该参数转化为 bytes 类型:

1
2
3
4
5
6
import urllib.parse
import urllib.request

data = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8') # 利用 urlencode 将字典转换为字符串
response = urllib.request.urlopen('https://www.httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
timeout 参数

用于设置超时时间,单位为秒。

其支持 HTTP, HTTPS, FTP 请求。

通过 try ... expect ... 实现当一个网站长时间未响应就跳过对它的抓取:

1
2
3
4
5
6
7
8
9
import socket
import urllib.request
import urllib.error

try:
response = urllib.request.urlopen('https://www.httpbin.org/get', timeout=0.1)
except urllib.erro.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')

常理来说,0.1 秒不可能得到服务器的响应。

Request

Request 是一个类,将向 urlopen 传入一个 url 改为一个对象,方便配置参数:

1
2
3
4
5
import urllib.request

request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utd-8'))

Request 类构造:

1
class usrllib.request.Request(url, data=None, headers={}, origin_req_host=None, unvarifiable=False, method=None)

添加请求头最常见的办法是通过修改 User-Agent 来伪装浏览器。

通过 add_header 方法添加 headers 的方式:

1
2
req = request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
高级用法

如 Cookie 处理、代理设置等。

使用 Handler 工具.

urllib.request 模块里的 BaseHandler 类是其他所有 Handler 类的父类。

另一个比较重要的类是 OpenerDirecctor, 简称为 Opener, 其提供 open 方法,该方法返回的相应类型和 urlopen 一致.

一般用 Handler 类来创建 Opener 类。

验证

在访问某些网站是会有认证窗口。表明其启用了基本身份认证(HTTP Basic Access Authentication).

利用 HTTPBasicAuthHandler 模块完成请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError

username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center/'

p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)

try:
result = opener.open(url)
html = result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)
代理

2 处理异常

当出现问题时,request 模块便会抛出 error 模块中定义的异常.

URLError

URLErrot 类来自 urllib 库中的 error 模块,继承自 OSError 类,是 error 异常模块的基类.

其有 reason 属性,返回错误原因。

1
2
3
4
5
6
from urllib import request, error

try:
response = request.urlopen('https://cuiqigcai.com/404')
expect URLError as e:
print(e.reason)

HTTPError

是 URLError 的子类,用于处理 HTTP 请求错误.

其有3个属性:

  • code, 返回 HTTP 状态码
  • reason, 返回错误原因
  • headers, 返回请求头
    因为 HTTPError 是 URLError 的子类,所以可以先捕获子类的错误再捕获父类的错误。

3 解析链接

urllib 库中的 parse 模块,其定义了处理 URL 的标准接口.

urlparse

用于实现 URL 的识别和分段的方法。也就是说,一个 URL 是有很多部分组成的,这个方法可以把各个部分区分开. 其利用 URL 中特定的分隔符。

1
2
3
4
5
from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=comment')
print(type(result))
print(result)

一个标准的链接格式:

1
scheme://netloc/path;params?query#fragment

urlparse 的 API 用法:

1
urllib.parse.urlparse(urlstring, scheme'', allow_fragments=True)

三个参数:

  • urlsring, 即 url
  • scheme, 填写协议,只有在 url 中不包含协议时生效
  • allow_fragments, 是否省略掉 fragments 部分
    urlparse 的结果是一个元组,可以用属性名或索引来获取。

urlunparse

用于构造 url, 其接受的参数是一个可迭代对象,且长度为6.

1
2
3
4
from urllib import urlunparse

data = ['https', 'www.baidu.com', 'index.html', 'user'. 'a=6', 'comment']
print(urlunparse(data))

urlsplit

类似于 urlparse.

urlunsplit

类似于 urlunparse.

urljoin

可以实现链接的解析,拼合与生成。

urlencode

其在构造 GET 请求时非常有用, 其将字典转换为 url 参数:

1
2
3
4
5
6
7
8
9
from urllib import urlencode

params = {
'name': 'germey',
'age': 25
}
base_url = 'https://www.baidu.com'
url = base_url + urlencode(params)
print(url)

pase_qs

将 GET 请求参数传回字典

parse_qsl

quote

将内容转化为 url 编码模式。

unquote

4 分析 Robots 协议

使用 urllib 库中的 robotparse 模块进行分析.

Robots 协议

Robots 协议也称爬虫协议,全名为 Robots Exclusion Protocol, 来告诉爬虫和搜索1引擎哪些页面可以爬取。通常是一个 robots.txt 文本文件,位于网站的根目录下,

搜索爬虫在访问一个站点时,首先会检查这个站点根目录下是否存在 robots.txt 文件,若存在, 则在其定义的爬取范围内爬取。

robots.txt 样例:

1
2
3
User-agent: *
Disallow: /
Allow: /public/

其限制了只能爬取 public 目录。

User-agent 描述了搜索爬虫的名称。

Disallow 指定了不允许爬虫爬去的目录,/ 表示不允许爬取所有页面.

Allow 一般不单独使用。

允许爬虫访问所有目录的代码:

1
2
User-agent: *
Disallow:

只允许某一个爬虫访问所有目录:

1
2
3
4
User-agent: WebCrawler
Disallow:
User-agent: *
Disallow: /

爬虫名称

爬虫有固定名称。

robotparser

使用 robotparser 模块来解析 robots.txt 文件。

该模块提供了 RobotFileParser 类,其根据某网站的 robots.txt 文件来判断一个爬取爬虫是否有权限爬取这个网站。

RobotFileParser 类的声明:

1
urllib.robotparse.RobotFileParser(url='')

传入 robots.txt 文件的链接。

RobotFileParser 类的常用方法:

  • set_url
  • read
  • parse
  • can_fetch
  • mtime
  • modified
    1
    2
    3
    4
    5
    6
    7
    8
    from urllib.robotparser import RobotFileParser

    rp = RobotFileParser()
    rp.set_url('https://www.baidu.com/robots.txt')
    rp.read()
    print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
    print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
    print(rp.can_fetch('Googlebot, 'https://www.baidu.com'))

2.2 requests 的使用

requests 库用 get 方法请求网页。

1
2
3
import requests

r = requests.get('https://www.baidu.com')

GET 请求

利用 requests 库构建 GET 请求的方法.

基本实例

利用 params 参数传递信息:

1
2
3
4
5
6
7
import requests

data = {
'name' : 'germey'
'age' : 25
}
r = request.get('https://www.httpbin.org/get', params=data)

调用 json 方法可以将返回结果(JSON格式的字符串)转化为字典。

抓取网页

抓取二进制数据

如抓取图片、音频、视频等文件。

Response 对象的两个属性:

  • text, 其为 Unicode 文本
  • content, 其为 bytes 文本

添加请求头

2.3 正则表达式

Python 的 re 库提供了整个正则表达式的实现.

match

从字符串开头进行匹配,若开头不一致则匹配失败。

1
result = re.match('Hello', content)

第一个参数为 regex, 第二个为匹配对象。

非贪婪的写法为 .*?

修饰符

第三个参数为修饰符:

  • re.I, 大小写不敏感
  • re.L, 本地化识别匹配
  • re.M, 多行匹配,影响 ^ 和 $
  • re.S, 匹配内容包括换行符在内的所有字符
  • re.U, 根据 Unicode 字符集解析字符
  • re.X, 书写表达式更灵活

这个方法比较类似 Perl 中的 match.

返回与正则表达式相匹配的第一个字符串.

使用 group 方法提取括号捕获的内容。

findall

返回的结果是列表类型,能够获取与正则表达式相匹配的所有字符串。

sub

用于修改文本.

应该是作用于所有匹配的文本。

1
content = re.sub(regex, replacement, content)

compile

将正则字符串编译成正则表达式对象,方便复用:

1
2
pattern = re.compile(regex)
result = re.search(pattern, content)

compile 同样可以传入修饰符。

2.4 httpx 的使用

urllib 库和 requests 库只能爬取支持 HTTP/1.1 的网站。

支持 HTTP/2.0 的请求库,具有代表性的是 hyper 和 httpx.

查看网站的 Network 面板,如果 Protocol 一列是 h2, 证明请求所用的协议是 HTTP/2.0

第3章 网页数据的解析提取

3.1 XPath 的使用

XPath 的全称为 XML Path Language, 即 XML 路径语言。用来在 XML 文档中查找信息。

概览

提供路径选择表达式和内建函数。

常用规则

  • nodename : 选取此节点的所有子节点
  • / : 选取当前节点的直接子节点
  • // : 选取当前节点的子孙结点
  • . : 选取当前节点
  • .. : 选取当前节点的父节点
  • @ : 选取属性
  • [@attrib] : 选取具有给定属性的所有元素
  • [tag] : 选取所有具有指定元素的直接子节点
  • [tag=’text’] : 选取所有具有指定元素并且文本内容是 text 的节点
    如:
    1
    //title[@lang='eng']
    每一个规则后面跟的应该就是变量名,这里代表所有名称为 title,同时属性 lang 的值为 eng 的节点.

安装

1
pip install lxml

使用

Python 中三个引号允许字符串跨多行使用,包括换行,tab等特殊字符。引号可以为单引号也可为双引号.

lxml 库中的 etree 模块可以自动修正 HTML 文本.

构造解析对象:

1
2
3
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))

text 是一段 HTML 代码字符串,这里 HTML 是一个类,用于初始化这个文本。

tostring 方法输出修正后的 HTML 文本。但其为 bytes 类型。

也可直接解析文本文件:

1
2
3
4
5
from lxml import etree 

html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

这里的 parse 方法的第一个参数为 HTML 文件的路径.

所有节点

1
2
3
4
5
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)

这里 xpath 方法的参数就是 XPath 表达式,其中 * 匹配所有节点。

结果返回一个列表。其中每个元素都是 Element 类型。

子节点

选择当前 li 节点的所有直接子节点 a:

1
result = html.path('//li/a')

父节点

1
result = html.xpath('//a[@href="link4.html"]/../@class')

//a[@href="link4.html"] 的含义是,节点为 a, 其属性 href 的值为 link4.html.

属性匹配

文本获取

用 XPath 中的 text 方法可以得到节点中的文本:

1
result = html.xpath('//li[@class="item-0"]/text()')

属性获取

使用 @ 符号:

1
result = html.xpath('//li/a/@href')

直接的 @href 是获取 href 属性的值,[@href="link4.html"] 是限定某个属性.

属性多值匹配

也就是某个属性的值有多个, 但只限定一个, 使用 contain 方法:

1
result = html.xpath('//li[contains(@class, "li")]/a/text()')

第一个参数为属性名,第二个参数为值.

多属性匹配

需要根据多个属性来确定一个节点, 使用 and 连接多个属性:

1
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')

XPath 中的运算符

  • or
  • and
  • mod
  • |
  • div
  • =
  • !=
  • <
  • <=
  • =

按序选择

有多个匹配,但只选取一个:

1
2
3
4
result = html.xpath('//li[1]/a/text()')  # 1 即选择匹配到的第一个 
result = html.xpath('//li[last()]/a/text()')
result = html.xpath('//li[position()<3]/a/text()')
result = html.xpath('//li[last()-2]/a/text()')

这时 [] 内应该为位置的 index.

节点轴选择

轴的形式为 name::

如祖先节点轴: ancestor::

3.2 Beautiful Soup 使用

Beautiful Soup 借助网页1的结构和属性等特性来解析网页。

简介

Beautiful Soup 是 Python 的一个 HTML 或 XML 的解析库。

Beautiful Soup 会自动将输入文档转换为 Unicode 编码,将输出文档转换为 utf-8 编码。

解析器

Beautiful Soup 中的解析器及使用方法:

  • Python 标准库, BeautifulSoup(markup, 'html.parser')
  • LXML HTML, BeautifulSoup(markup, 'lxml')
  • LXML XML, BeautifulSoup(markup, 'xml')
  • html5lib, BeautifulSoup(markup, 'html5lib')

LXML 解析器能解析 HTML 和 XML.

例如:

1
2
3
4
from bs4 import BeautifulSoup

soup = BeautifulSoup('<p>Hello<p>', 'lxml')
print(soup.p.string)

基本用法

1
2
3
4
5
from bs4 import BeautifulSoup

soup = BeautifulSoup(html, 'lxml')
print(soup.prettify())
print(soup.title.string)

BeautifulSoup 是一个对象,其第一个参数为 HTML 字符串,第二个参数为解析器的类型。

用到的几个方法:

prettify()

把要解析的字符串以标准的缩进格式输出。

也就是让我们以美观的方式看到最开始要处理的字符串.

若 HTML 字符串不标准,可以自动更正。

title

soup.title.string 其中 title 是选出 HTML 中的 title 节点,调用 string 属性就得到节点内的文本.

节点选择器

实际上,直接调用节点的名称就能选择节点,如 head, p 等。在调用 string 属性就可以得到文本。不用 string 打印的就是 HTML 代码。

形式为:

1
bs4.element.tag

Tag 就是属性。

当有多个节点时,这种方法只能选取第一个匹配项。

提取信息

获取节点名称

使用 name 属性。

获取属性

使用 attrs 属性,其返回字典:

1
2
print(soup.p.attrs)
print(soup.p.attrs['name'])

获取内容

使用 string 属性。

嵌套选择

对付节点里面有节点:

1
print(soup.head.title.string)

关联选择

即以某一点为当前节点,然后选取父节点之类的。

子节点和子孙节点

调用 contents 属性,获取直接子节点, 其得到的结果是直接子节点组成的列表。

调用 children 属性能得到相应的结果。其返回结果是生成器类型。

1
2
3
4
5
6
from bs4 import BeautifulSoup

soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children)
print(i, child)

这里 enumerate() 函数是 Python 内置函数,其参数为可迭代对象, 可用于计数,返回值是 enumerate 对象,可同时获取参数的索引和值,比如这里的 i 就是索引,child 就是值。

得到子孙结点,调用 descendants 属性。其返回结果也是生成器类型。

父节点和祖先节点

获取父节点,用 parent 属性。

获取所有祖先节点,用 parents 属性。

兄弟节点

下一个兄弟节点用 next_siblings 属性。

上一个用 previous_siblings 属性.

提取信息

同样用 string 等。

方法选择器

方便灵活查询.

find_all

查询所有符合条件的元素,可以给它传入一些属性或文本:

1
find_all(name, attrs, recursive, text, **kwargs)

其返回值是列表。

name
1
find_all(name=head)
attrs

传入的参数为字典类型:

1
find_all(attrs={'id' : 'list'})
text

可以传入字符串,也可以是正则表达式:

1
print(soup.find_all(text=re.compile('link')))

find

返回单个元素.

其它

使用方法和 find_all 相同:

  • find_parents
  • find_parent
  • find_next_siblings
  • find_previous_siblings
  • find_all_next
  • find_next
  • find_all_previous
  • find_previous

CSS 选择器

3.3 pyquery 的使用

适合于喜欢用 CSS 选择器。

3.4 parsel 的使用

XPath 和 CSS 选择器一起使用。

第4章 数据的存储

4.2 JSON 文件存储

JSON 全称为 JavaScript Object Notation, 即 JavaScript 对象标记,通过对象和数组的组合表示数据.

对象和数组

对象在 JavaScript 中是指用花括号 {} 包围起来的内容,数据结构是 {key1: value1, key2: value2 ...}. 在面向对象语言中,key 表示对象的属性、value 表示属性对应的值。

数组在 JavaScript 中是指用方括号 [] 包围起来的内容,数据结构是 ["java", "javascript", ...] 这种索引结构。

读取 JSON

使用 Python 的 json 库。

loads 方法,将文本字符串转化为 JSON 对象(即 Python 中的列表和字典的嵌套组合).

dumps 方法,将 JSON 对象转化为文本字符串。

获取键值的方法有两种:

1
2
data[0]['name']
data[0].get('name')

get 方法也可传入第二个参数,其为不存在键名时返回的值。

JSON 的数据需要用双引号包裹而不是单引号。

load 方法的参数是一个文本操作对象:

1
2
3
4
import json

data = json.load(open('data.json', encoding='utf-8'))
print(data)

输出 JSON

如果想保存 JSON 对象的缩进格式,往 dumps 方法中添加 indent 参数,代表缩进字符的个数。

1
2
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))

要想输出中文,需指定参数 ensure_asciiFalse:

1
2
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))

也有 dump 方法:

1
json.dump(data, open('data.json', 'w', endocing='utf-8'), indent=2, ensure_ascii=False)

4.4 MySQL 存储

使用 PyMySQL 库操作 MySQL 数据库。

第5章 Ajax 数据爬取

Ajax 的全称为 Asynchronous JavaScript and XML, 即异步的 JavaScript 和 XML.

如: 页面下滑时慢慢加载就是在向 Ajax 获取数据。

5.1 基本原理

分为三步:

  • 发送请求
  • 解析内容
  • 渲染页面

Ajax 是由 JavaScript 实现的。

5.2 Ajax 分析方法

Ajax 有其独特的请求类型,叫做 xhr

5.3 Ajax 分析与爬取实战

在 HTML 中,只能看到源码引用的一些 JavaScript 和 CSS 文件,并没有观察到其他有用信息,说明其在后台调用了 Ajax 接口。

字典中的 get() 利用键来获取值。

第6章 异步爬虫

6.1 协程的基本原理

基础知识

协程(coroutine), 是一种运行在用户态的轻量级线程。其拥有自己的寄存器和上下文。

asyncio 库用法

几个概念:

  • event_loop: 事件循环,相当于无限循环,可将函数注册在其上,当满足条件时调用
  • coroutine: Python 中指代协程对象类型,可将其注册到事件循环上
  • task: 任务,对协程对象的包装
  • future: 代表将来执行或没有执行的任务的结果,本质上同 task

定义协程

async 关键字,用于定义一个方法, 其为一个无法直接执行的协程对象,必须将此对象注册到事件循环中才可以执行。

1
2
3
4
5
6
7
8
9
10
11
12
import asycio

async def execute(x):
print('Number:', x)

coroutine = execute(1)
print('Coroutine:', coroutine)
print('After calling execute')

loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('After calling loop')

这里 get_event_loop() 方法创建了一个事件循环, 即其返回值是一个事件循环。

run_until_complete() 方法将协程对象注册到事件循环中. 也就是执行。其在内部隐式将协程对象变为 task.

显式将协程对象封装为task:

1
task = loop.create_task(coroutine)

另外定义 task 的方法, 使用 asyncio 包的 ensure_future 方法:

1
task = asyncio.ensure_future(coroutine)

绑定 callback

即在 task 完成时就会调用的方法, 使用 task 对象的 add_done_callback() 方法:

1
  

Python Web Spider Notes
http://example.com/2022/08/23/Python-Web-Spider-Notes/
作者
Jie
发布于
2022年8月23日
许可协议