整理:QT
最近在学python,了解到了爬虫,下了北京理工大学嵩天老师的课程,顺便学了用Typora
做笔记,还用第一种技术路线爬了链家的二手房信息,利用百度地图做了热力图,感觉还挺好玩。不过裸辞在家…还是挺艰辛。记笔记勉励自己,也可分享给学友共同进步,个人水平有限,如有错漏还请见谅,如果侵犯到任何人的权益,请告知。大部分内容来源于网络,如需沟通请下方微信或QQ:604615850联系,侵删。
正文开始
The website is API,掌握定向网络数据爬取和网页解析的基本能力:
Requests
:自动爬去HTML页面自动网络请求提交;robots.txt
:网络爬虫排除标准;Beautiful Soup
:解析HTML页面;Re
:正则表达式详解提取页面关键信息;Scrapy
:网络爬虫原理,专业爬虫框架;
Request库与HTTP协议
request库主要方法
方法 | 说明 |
---|---|
requests.request() |
构造一个请求,支撑以下个方法的基础方法 |
requests.get() |
获取HTML网页的主要方法,对应于HTTP的GET |
requests.head() |
获取HTML网页头信息的方法,对应于HTTP的HEAD |
requests.post() |
向HTML网页提交POST请求的方法,对应于HTTP的POST |
requests.put() |
向HTML网页提交PUT请求的方法,对应于HTTP的PUT |
requests.patch() |
向HTML网页提交局部修改请求,对应于HTTP的PATCH |
requests.delete() |
向HTML网页提交删除请求,对应于HTTP的DELETE |
为了更好理解上面的方法,我们先了解一下HTTP协议。
HTTP协议
HTTP(Hypertext Transfer Protocol)协议,即,超文本传输协议;
是一种基于”请求与响应“模式的、无状态(前后两次请求之间无关联)的应用层协议;
采用URL作为定位网络资源的标识:URL格式为
http://host[:port][path]
;host:合法的Internet主机域名或IP地址;
port:端口号,缺省端口为80;
path:请求资源的路径;
URL是通过HTTP协议存取资源的INTERNET路径,一个URL对应一个数据资源。
HTTP协议对资源的操作(与requests对应方法对应);
方法 | 说明 |
---|---|
GET | 请求获取URL位置的资源 |
HEAD | 请求获取URL位置资源的响应消息报告,即获得该资源的头部信息 |
POST | 请求获取URL位置的资源后附加新的数据 |
PUT | 请求向URL位置存储一个资源,覆盖原URL位置的资源 |
PATCH | 请求局部更新URL位置的资源,即改变该处资源的部分内容 |
DELETE | 请求删除URL位置存储的资源 |
response对象的属性
属性 | 说明 |
---|---|
r.starus_code |
HTTP请求的返回状态,200表示连接成功,404表示失败 |
r.text |
HTTP响应内容的字符串形式,即,URL对应的页面内容 |
r.encoding |
从HTTP header中猜测的响应内容编码方式 |
r.apparent_encoding |
从内容中分析出的响应内容编码方式(备选编码方式) |
r.content |
HTTP响应内容的二进制形式 |
1 | import requests |
r.encoding
:如果header中不存在charset
字段,则认为编码为ISO-8859-1
;r.apparent_encoding
:根据网页内容分析出的编码方式;
同时包含requests
的全部信息:
1 | print(r.request.headers) |
requests.request()详解
此方法为最基本的方法,可以构造一个请求,支撑其它各方法的基础方法,格式如下:
requests.request(method,url,**kwargs)
- method:请求方式,对应get/put/post等7种;
- url:拟获取页面的url链接;
- kwargs:控制访问参数,共13个,均为可选项,如下:
- params:字典或字节序列,作为参数增加到url中;
- data:字典,字节序列或文件对象,作为Request的内容;
- json:JSON格式的数据,作为Request的内容;
- headers:字典,HTTP定制头(模拟浏览器进行访问);
- cookies:字典或CpplieJar,Request中的cookie;
- auth:元组,支持HTTP认证功能;
- files:字典类型,传输文件;
- timeout:设定超时时间,秒为单位;
- proxies:字典类型,设定访问代理服务器,可以增加登陆认证;
- allow_redirects:True//False,默认为True,重定向开关;
- stream:True/False,默认为True,获取内容立即下载开关;
- verify:True/False,默认为True,认证SSL证书开关;
- cert:本地SSL证书路径。
1 | import requests |
robots协议
Robots Exclusion Standard 网络爬虫排除标准
- 作用:网站告知网络爬虫哪些页面可以抓取,哪些不行;
- 形式:在网站根目录下的
robots.txt
文件;
1 | 案例:京东的Robots协议:https://www.jd.com/robots.txt |
Robots的遵守方法
协议的使用:
- 网络爬虫:自动或人工识别robots.txt,再进行内容爬取;
- 约束性:Robots协议是建议但非约束性网络爬虫可以不遵守,但村子法律风险。
对Robots协议的理解:
爬取网页,玩转网页 | 爬取网站,爬取系列网站 | 爬取全网 |
---|---|---|
访问量很小:可以遵守 | 非商业且偶尔:建议遵守 | 必须遵守 |
问量较大:必须遵守 | 商业利益:必须遵守 | - |
代码框架及异常处理
1 | #爬取网页的通用代码框架及异常处理 |
异常 | 说明 |
---|---|
requests.RequestException |
There was an ambiguous exception that occurred while handling your request. |
requests.ConnectionError |
网络连接错误异常,如DNS查询失败,拒绝连接等 |
requests.HTTPError |
HTTP错误异常 |
requests.URLRequired |
URL缺失异常 |
requests.TooManyRedirects |
超过最大重定向次数,产生重定向错误 |
requests.ConnectTimeout |
连接远程服务器超时异常 |
requests.Timeout |
请求URL超时,产生超时异常 |
requests.ReadTimeout |
The server did not send any data in the allotted amount of time. |
应用实例
访问亚马逊中国:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import requests
def getHTMLText(url):
kv = {'user-agent':'Mozilla/5.0'}#网站过滤爬虫,模拟浏览器访问
try:
r = requests.get(url,headers=kv)
print(r.status_code,r.encoding,r.apparent_encoding)
r.raise_for_status()#如果r.starus_code不是200,产生一个requests.HTTPError
# r.encoding = r.apparent_encoding
print(r.request.headers)
return r.text
except:
return "Errors occur"
if __name__ == '__main__':
# url = 'https://item.jd.com/6946631.html'
url='https://www.amazon.cn/dp/B07C2S9ZYM/ref=sr_1_1?s=wireless&ie=UTF8&qid=1525140296&sr=1-1'
print(getHTMLText(url)) #503 拒绝某些客户端的连接
## 200 UTF-8 ISO-8859-2
## {'user-agent': 'Mozilla/5.0', 'Accept': '*/*', 'Connection': 'keep-alive', 'Accept-Encoding': 'gzip, deflate'}- 百度提交关键字
1
2
3
4
5
6
7
8
9
10
11
12import requests
try:
kv={'wd':'Python'}
url='https://www.baidu.com/s'
r = requests.get(url,params=kv)
print(r.request.url)
r.raise_for_status()
print(len(r.text))
except:
print("Errors occur")
# https://www.baidu.com/s?wd=Python
# 227- 网络图片的爬取与存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import requests
import os
url = 'https://news.nationalgeographic.com/content/dam/news/2017/07/13/01-tardigrade.adapt.1190.1.jpg'
root = './mypic/'
#注:ubuntu中根目录”/“;当前目录“./”;当前目录的上一级目录(如果有上一级目录的话)“../”
path = root + url.split('/')[-1].replace('.','_') + ".jpg"
try:
if not os.path.exists(root):
os.mkdir(root)
if not os.path.exists(path):
r = requests.get(url)
r.raise_for_status()
with open(path,'wb') as f:
f.write(r.content)
f.close()
print("Saved successfully")
else:
print("Already exited")
except:
print("Errors occur")IP
地址归属地的自动查询
1
2
3
4
5
6
7
8
9
10
11
12
13import requests
#'http://www.ip138.com/ips138.asp?ip=202.204.80.112&action=2'
url = 'http://www.ip138.com/ips138.asp?'
kv = {'ip':'202.204.80.112','action':'2'}
try:
r = requests.get(url, params=kv) #, headers=headkv
r.raise_for_status()#如果r.starus_code不是200,产生一个requests.HTTPError
r.encoding = r.apparent_encoding
print(r.text[7250:7380])
print("Visit successfully")
except:
print("Errors occur")
网络爬虫引发的问题
- 引发的问题
- 爬虫骚扰,增加服务器负担;
- 爬虫的法律风险;
- 爬虫泄露隐私,突破简单的访问控制。
爬取网页,玩转网页 | 爬取网站,爬取系列网站 | 爬取全网 |
---|---|---|
小规模,数据量小,爬取速度不敏感 | 中规模,数据规模较大,爬取速度敏感 | 大规模,搜索引擎爬取速度关键 |
requests库 | scrapy库 | 定制开发 |
- 限制网络爬虫的技术手段:
- 来源审查:检查来访HTTP协议头的
user-agent
域,判断user-agent
进行限制,只响应浏览器或友好爬虫的访问; - 发布公告:通过robots.txt协议,告知所有爬虫,网站允许的爬取策略,要求爬虫遵守。
- 来源审查:检查来访HTTP协议头的
BeautifulSoup库
BeautifulSoup
库及安装
请求把数据返回来之后就要提取目标数据,不同的网站返回的内容通常有多种不同的格式,
JSON
:有类型的键值对key:nalue
,适合程序处理;移动应用云端和节点的信息通信,无注释;XML
:最早的通用信息标记语言,标签占用过多;Internet上的信息交互与传递;YAML
:无类型的键值对,文本信息比例最高,可读性好;各类系统的配置文件,有注释易读;HTML
文档从属与XML
,现在就来讲讲如何从 HTML 中提取出感兴趣的数据,BeautifulSoup
是一个用于解析 HTML 文档的 Python 库,通过BeautifulSoup
,你只需要用很少的代码就可以提取出 HTML 中任何感兴趣的内容,此外,它还有一定的 HTML 容错能力,对于一个格式不完整的HTML 文档,它也可以正确处理。Beautiful Soup
是python的一个库,最主要的功能是从网页抓取数据。
安装:pip install beautifulsoup4
HTML 标签初识
学习 BeautifulSoup4 前有必要先对 HTML 文档有一个基本认识,如下代码,HTML 是一个树形组织结构。
1 | <html> |
- 通过预定义的
<>...</>
标签形式组织不同类型的信息; - 它由很多标签(Tag)组成,比如 html、head、title等等都是标签;
- 一个标签对构成一个节点,比如 … 是一个根节点;
- 节点之间存在某种关系,比如 h1 和 p 互为邻居,他们是相邻的兄弟(sibling)节点;
- h1 是 body 的直接子(children)节点,还是 html 的子孙(descendants)节点;
- body 是 p 的父(parent)节点,html 是 p 的祖辈(parents)节点;
- 嵌套在标签之间的字符串是该节点下的一个特殊子节点,比如 “hello, world” 也是一个节点,只不过没名字;
使用 BeautifulSoup
构建一个 BeautifulSoup
对象需要两个参数,第一个参数是将要解析的 HTML 文本字符串,第二个参数告诉 BeautifulSoup
使用哪个解析器来解析 HTML。解析器负责把 HTML 解析成相关的对象,而 BeautifulSoup
负责操作数据(增删改查)。”html.parser”
是Python内置的解析器,”lxml”
则是一个基于c语言开发的解析器,它的执行速度更快,不过它需要额外安装。
通过BeautifulSoup
对象就可以定位到 HTML 中的任何一个标签节点。
1 | from bs4 import BeautifulSoup |
BeatifulSoup
将 HTML 抽象成为 4 类基本元素,分别是Tag
, Attributes
,NavigableString
, Comment
。每个标签节点就是一个Tag
对象,NavigableString
对象一般是包裹在Tag
对象中的字符串,BeautifulSoup
对象代表整个 HTML 文档。例如:
1 | type(soup) |
Tag
每个 Tag 都有一个名字,它对应 HTML 的标签名称.
1 | soup.h1.name |
标签还可以有属性,属性的访问方式和字典是类似的,它返回一个列表对象。
1 | 'class'] soup.p[ |
NavigableString
获取标签中的内容,直接使用 .stirng
即可获取,它是一个 NavigableString
对象,你可以显式地将它转换为 unicode
字符串。
1 | soup.p.string |
数据提取
如何从 HTML 中找到我们关心的数据?BeautifulSoup
提供了两种方式,一种是遍历,另一种是搜索,通常两者结合来完成查找任务。
遍历文档树
顾名思义,就是是从根节点 html 标签开始遍历,直到找到目标元素为止,遍历的一个缺陷是,如果你要找的内容在文档的末尾,那么它要遍历整个文档才能找到它,速度上就慢了,主要的遍历方向有三种:
- 下行遍历
属性 | 说明 |
---|---|
.contents |
子节点的列表,将<tag> 所有儿子节点存入列表 |
.children |
子节点的迭代类型,与.conents 类似,用于循环遍历儿子节点 |
.descendants |
子孙节点的迭代类型,包含所有子孙节点,用于循环遍历 |
- 上行遍历
属性 | 说明 |
---|---|
.parent |
节点的父亲标签 |
.parents |
节点先辈标签的迭代类型用于循环遍历先辈节点 |
- 平行遍历(同一个父亲节点下)
属性 | 说明 |
---|---|
.next_sibling |
返回按照HTML文本顺序的下一个平行节点标签 |
.previous_sibling |
返回按照HTML文本顺序的上一个平行节点标签 |
.next_siblings |
迭代类型,返回按照HTML文本顺序的后续所有平行节点标签 |
.previous_siblings |
迭代类型,返回按照HTML文本顺序的前序所有平行节点标签 |
通过遍历文档树的方式获取标签节点可以直接通过.标签名
的方式获取,内容也是一个节点,这里就可以用 .string
的方式得到。遍历文档树的另一个缺点是只能获取到与之匹配的第一个子节点,例如,如果有两个相邻的 p 标签时,第二个标签就没法通过.p
的方式获取,这是需要借用 next_sibling
属性获取相邻且在后面的节点。此外,比如:.contents
获取所有子节点,.parent
获取父节点,例如:
1 | #获取body标签: |
搜索文档
搜索文档树是通过指定标签名来搜索元素,另外还可以通过指定标签的属性值来精确定位某个节点元素,最常用的两个方法就是
find
和find_all
。这两个方法在BeatifulSoup
和Tag
对象上都可以被调用。find_all( name , attrs , recursive , string , **kwargs )
- 返回一个列表类型,存储查找的结果;
name
:对标签名称的检索字符串;attrs
:对标签名称属性值的检索字符串,可标注属性检索;recursive
:是否对子孙全部检索,默认为True;string
:<>...</>
中字符串区域的检索字符串;- 简写形式
<tag>(..)等价于<tag>.find_all(..)
find_all 的返回值是一个 Tag 组成的列表,方法调用非常灵活,所有的参数都是可选的:
第一个参数 name 是标签节点的名字;
1
2"title") soup.find_all(
[<title>hello, world</title>]第二个参数是标签的class属性值;
1
2
3"p","big") soup.find_all(
#class_="big",因为class是Python关键字,所以这里指定为 class_ 。
[<p class="big" id="key1"> 第二个p标签</p>]kwargs 是标签的属性名值对,例如:查找有href属性值为 “http://www.fool.com" 的标签;
1
2"http://www.fool.com") soup.find_all(href=
[<a href="http://www.fool.com">python</a>]正则表达式;
1
2
3import re
"^http")) soup.find_all(href=re.compile(
[<a href="http://foofish.net">python</a>]布尔值(True/Flase),表示有属性或者没有该属性;
1
2
3
4"key1") soup.find_all(id=
[<p class="big" id="key1"> 第二个p标签</p>]
True) soup.find_all(id=
[<p class="big" id="key1"> 第二个p标签</p>]遍历和搜索相结合查找,先定位到 body 标签,缩小搜索范围,再从 body 中找 a 标签。
1
2
3body_tag = soup.body
'a') body_tag.find_all(
[<a href="http://www.fool.com">python</a>]
find( name , attrs , recursive , string , **kwargs )
find 方法跟 find_all 类似,唯一不同的地方是,它返回的单个 Tag 对象而非列表,如果没找到匹配的节点则返回 None。如果匹配多个 Tag,只返回第0个。
1
2
3
4
5
6"a") body_tag.find(
<a href="http://foofish.net">python</a>
"p") body_tag.find(
<p class="bold">如何使用BeautifulSoup</p>
"p") body_tag.find_all(
[<p class="bold">如何使用BeautifulSoup</p>, <p class="big" id="key1"> 第二个p标签</p>]get_text()
获取标签里面内容,除了可以使用
.string
之外,还可以使用get_text
方法,不同的地方在于前者返回的一个NavigableString
对象,后者返回的是unicode
类型的字符串。但这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19'p').get_text() p1=body_tag.find(
type(p1)
<class 'str'>
>>> p1
'如何使用BeautifulSoup'
>>> p2=body_tag.find('p').string
type(p2)
<class 'bs4.element.NavigableString'>
>>> p2
'如何使用BeautifulSoup'
#返回包含所有文版内容包括子酸tag中的内容
>>> soup.find('body').get_text()
'\nBeautifulSoup\n如何使用BeautifulSoup\n 第二个p标签\npython\n'
#通过参数指定tag的文本内容的分隔符
>>> soup.find('body').get_text("|")
'\n|BeautifulSoup|\n|如何使用BeautifulSoup|\n| 第二个p标签|\n|python|\n'
#去除获得文本内容的前后空白
>>> soup.find('body').get_text("|",strip=True)
'BeautifulSoup|如何使用BeautifulSoup|第二个p标签|python'实际场景中我们一般使用 get_text 方法获取标签中的内容。
扩展方法:
方法(参数同find_all() ) |
说明 |
---|---|
<>.find() |
搜索只返回一个结果,字符串类型 |
<>.find_parents() |
在先辈节点中搜索,返回列表类型 |
<>.find_parent() |
在先辈节点中返回一个结果,字符串类型 |
<>.find_next_siblings() |
在后续平行节点中搜索,返回列表类型 |
<>.find_next_sibling() |
在后续平行节点中返回一个结果,字符串类型 |
<>.find_previoust_siblings() |
在前序平行节点中搜索,返回列表类型 |
<>.find_previoust_sibling() |
在前序平行节点中返回一个结果,字符串类型 |
小结
BeatifulSoup
是一个用于操作 HTML 文档的 Python 库,初始化 BeatifulSoup
时,需要指定 HTML 文档字符串和具体的解析器。BeatifulSoup
有3类常用的数据类型,分别是 Tag
、NavigableString
和 BeautifulSoup
。查找 HTML元素有两种方式,分别是遍历文档树和搜索文档树,通常快速获取数据需要二者结合。
正则表达式库Re
正则表达式:regular expression,是用来简洁表达一组字符串的表达式。
- 通用的字符串表达框架;
- 简洁表达一组字符串的表达式;
- 针对字符串表达的“简洁”和“特征”思想的工具;
- 判断某字符串的特征属性。
正则表达式在文本处理中十分有用:
- 表达文本类型的特征(病毒、入侵等);
- 同时查找或替换一组字符串;
- 匹配字符串额全部或部分。
正则表达式的使用,需先编译,即将符合正则表达式语法的字符串转换成正则表达式特征。
正则表达式的语法
正则表达式由字符和操作符构成。
- 常用操作符:
操作符 | 说明 | 实例 |
---|---|---|
. |
表示任何单个字符 | a..b为所有以a头且以b尾的四字符串 |
[] |
字符集,对单个字符给出取值范围 | [a-z]表示a到z单个字符 |
[^] |
非字符集,对单个字符给出排除范围 | [^abc]表示非a或b或c的单个字符 |
* |
前一个字符0此或无限次扩展 | abc*表示ab,abc,abcc,abccc等 |
+ |
前一个字符1此或无限次扩展 | abc+表示abc,abcc,abccc等 |
? |
前一个字符0次或1次拓展 | abc?表示ab,abc |
| |
左右表达式任意一个 | abc|def表示abc或def |
{m} |
扩展前一个字符m次 | ab{2}c表示abbc |
{m,n} |
扩展前一个字符m至n次(含n) | ab{1,2}c表示abc,abbc |
^ |
匹配字符串开头 | ^abc表示abc且在一个字符串的开头 |
$ |
匹配字符串结尾 | abc$表示abc且在一个字符串的结尾 |
() |
分组标记,内部只能使用|操作符 | (abc|def)表示abc或def |
\d (\D ) |
数字 | 等价于[0-9] ([^0-9] ) |
\w (\W ) |
单词字符 | 等价于[A-Za-z0-9] ([^A-Za-z0-9] ) |
\s (\S ) |
与所有空白字符匹配 | 等价于[ \t\v\n\f\r] ([^ \t\v\n\f\r] ) |
\b (\B ) |
单词边界,在单词边界位置匹配空串 | \\b123\\b 或r\b123\b |
注释:正则表达式里的空格也作为常规字符,因此能与自己匹配。
经典正则表达式实例
^[A-Za-z]+$
:由26个字母组成的字符串^[A-Za-z0-9]+$
:由26个字母和数字组成的字符串^[-+]?\d+$
:整数形式的字符串[1-9]\d{5}
:中国境内邮政编码,6位[\u4e00-\u9fa5]
:匹配中文字符
re库的基本使用
re库是python的标准库,主要用于字符串匹配。
1 | #直接导入库 |
正则表达式的表示类型
- raw string类型(原生字符串类型):不包含转义符的字符串;
- string类型,有转义字符;
常用功能函数
函数 | 说明 |
---|---|
re.search() |
在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match() |
从一个字符串的开始位置起(前缀)匹配正则表达式,返回match对象 |
re.findall() |
搜索字符串,以列表类型返回全部能匹配的子串 |
re.split() |
将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer() |
搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 |
re.sub() |
在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
match
对象主要属性:
属性 | 说明 |
---|---|
.string |
待匹配的文本 |
.re |
匹配时使用的pattern对象(正则表达式) |
.pos |
正则表达式搜索文本的开始的位置 |
.endpos |
正则表达式搜索文本的结束位置 |
.group() |
获得匹配后的字符串 |
.start() |
匹配字符串再原始字符串的开始位置 |
.end() |
匹配字符串再原始字符串的结束位置 |
.span() |
返回(.start(),.end()) |
re.search(pattern,string,flags=0)
pattern
:正则表达式的字符串或原生字符串表示;string
:待匹配字符串;flags
:正则表达式使用时的控制标记;
常用标记 | 说明 |
---|---|
re.I re.IGNORECASE |
忽略正则表达式的大小写,[A-Z]能够匹配小写字符 |
re.M re.MULTILINE |
^ 操作符能够将给定字符串的每行当做匹配开始 |
re.S re.DOTALL |
. 操作符能够匹配所有字符,默认匹配处换行外的所有字符 |
1 | import re |
re.match(pattern,string,flags=0)
1
2
3
4
5
6
7
8import re
match=re.match('[1-9]\d{5}','BIT 100081')
if not match:
print('Return is None')
else:
print(match.groups())
# match需要从头开始匹配,所以这个例子返回None,
# 需要把字符调整'100081 BIT're.findall(pattern,string,flags=0)
1
2
3import re
ls = re.findall('[1-9]\d{5}','BIT100081 TSU100084')
print(ls)re.split(pattern,string,maxsplit=0,flags=0)
maxsplit
:最大分割数,剩余部分作为最后一个元素输出
1
2
3
4
5
6
7
8
9
10import re
ls=re.split('[1-9]\d{5}','BIT100081 TSU100084')
print(ls)
#output
['BIT', ' TSU', '']
##
ls=re.split('[1-9]\d{5}','BIT100081 TSU100084',maxsplit=1)
print(ls)
#output
['BIT', ' TSU100084']re.finditer(pattern,string,flags=0)
1
2
3
4
5
6
7#search只能找到第一个匹配结果,如果需要多个匹配用finditer
import re
for m in re.finditer('[1-9]\d{5}','BIT100081 TSU100084'):
print(m.group(0))
#output
100081
100084re.sub(pattern,repl,string,count=0,flags=0)
repl
:替换匹配字符串的字符串count
:匹配的最大替换次数
1
2
3
4
5import re
s=re.sub('[1-9]\d{5}',':zipcode','BIT100081 TSU100084')
print(s)
#output
BIT:zipcode TSU:zipcodematch.group()
模式里的组在正则表达式中一个重要的概念是组,使用圆括号括起来的模式段
(...)
,在考虑匹配时,它与被括起来的子模式匹配的串匹配,同时,圆括号还确定了一个被匹配的组。在一次成功匹配中,模式串里的各个组也都成功匹配,与它们匹配成功的那一组字符串将从1开始编号,而后可以通过调用
match.group(n)
获取,match.groups()
将得到这个从1开始的各个组匹配的串。作为特殊情况,组0就是与整个模式匹配的字符串,也可以通过match.group()
来获取。- 模式里各队圆括号确定的组按开括号的顺序编号,例如:
1
2
3
4
5
6
7import re
mat = re.search('.((.)e)f','abcdef')
print(mat.group())
print(mat.groups())
#output
cdef
('de', 'd')- 组的另一个重要用途,再匹配中应用前面的成功匹配,建立前后的部分匹配之间的约束关系,例如:
1
2
3
4
5
6
7
8
9import re
mat = re.search('(.{2}) \\1','bbb cc aa aab')
print(mat)
print(mat.group())
print(mat.groups())
#output
<_sre.SRE_Match object; span=(7, 12), match='aa aa'>
aa aa
('aa',)
re库的另一种等价用法
函数式用法:一次性操作
1
rst=re.search('[1-9]\d{5}','BIT 100081');
面向对象法:编译后的多次操作
1
2pat=re.compile('[1-9]\d{5}')
rst=pat.search('BIT 100081')re.compile(pattern,flags=0)
,将正则表达式的字符串形式编译成正则表达式对象pattern
:正则表达式的字符串或原生字符串表示;flags
:正则表达式使用时的控制标记;
1
regex = re.compile('[1-9]\d{5}')
等价的方法调用:
函数 | 说明 |
---|---|
regex.search() |
在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
regex.match() |
从一个字符串的开始位置起(前缀)匹配正则表达式,返回match对象 |
regex.findall() |
搜索字符串,以列表类型返回全部能匹配的子串 |
regex.split() |
将一个字符串按照正则表达式匹配结果进行分割,返回列表类型 |
regex.finditer() |
搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 |
regex.sub() |
在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
贪婪匹配和最小匹配
1 | match = re.search(r'PY.*N','PYANBNCNDN') |
python默认为贪婪匹配,即输出匹配长度最长的子串;
如何输出最小匹配?
1
2
3
4
5
6#在*后加?
match = re.search(r'PY.*?N','PYANBNCNDN')
#PYAN,PYANBN,PYANBNCN,PYANBNCNDN
print(match.group())
#output
PYAN最小匹配操作符:
操作符 | 说明 |
---|---|
*? |
前一个字符0次或无限次扩展,最小匹配 |
+? |
前一个字符1次或无限次扩展,最小匹配 |
?? |
前一个字符0次或1次扩展,最小匹配 |
{m,n}? |
扩展前一个字符m至n次(含n),最小匹配 |
应用实例(空)
XPath的介绍与配置
XPath是什么
- 是一门语言;
- 可以在XML文档中查找信息;
- 支持HTML;
- 通过元素和属性进行导航;
作用:
- 用来提取信息
- 比正则表达式厉害
- 比正则表达式简单
如何使用XPath
- 安装lxml库
from lxml import etree
Selector = etree.HTML(网页源代码)
- Selector.xpath(表达式)
XPath与HTML结构
- 树状结构
- 逐层展开
- 逐层定位
- 寻找独立节点
获取网页元素的XPath
- 手动分析法
- Chrome生成法
应对XPath提取内容
//
定位根节点/
往下层寻找/text()
提取文本内容/@xxx
提取属性内容
1 | text = """ |
XPath特殊用法:
- 包含字符串
contains(@属性名称,属性字符)
- 以相同的字符开头
start-with(@属性名称,属性字符相同部分)
- 标签套标签
string(.)
Scrapy库
Scrapy
是一个三方库,需用pip install scrapy --user
安装。但其并不是一个简单的函数功能库,而是一个爬虫框架。
- 爬虫框架是实现爬虫功能的一个软件结构和功能组合集合;
- 爬虫框架是一个半成品,能够帮助用户实现专业网络爬虫。
框架结构
“5个模块+2个中间件”结构:
上图的数字代表数据的流向,解释如下
- 引擎从Spider 获取初始Request对象;
- 引擎将获取的Request对象交给调度器Scheduler,并向Spider要下一个Request对象;
- 调度器将下一个Request对象交给引擎;
- 引擎将Request对象交给下载器Downloader,途径下载器中间件;
- 网页下载完成,下载器Downloader生成一个Response对象, 并经过下载中间件交给引擎;
- 引擎收到Response对象, 并交给Spider处理, 途径 Spider Middleware;
- Spider 处理Response 对象, 并将提取的结构化数据构成Item,同时生成新的Request对象,一并交给引擎, 途径 Spider Middleware;
- 引擎将Item 交给Item Pipeline 处理, 将Request对象交给调度器Scheduler,并继续向Spider要Request对象,直到没有Request对象可处理;
从上面的结构图可看出, Scrapy 框架以Engine 为核心来运转,当调度器中没有Request需要爬取时,爬取任务结束。图中Engine
、Downloader
和Scheduler
是已有实现,用户需要配置Spider
和Item Piplines
。其中Spider
用来想整个框架提供URL链接,同时要解析从网络页面获得的内容;Item Piplines
负责对提取的信息进行后处理。
用户需要或可能编写的模块如下:
Downloader Middleware
中间件:
- 实施
ENGINE
、DOWNLOADER
和SCHEDULER
之间进行用户可配置的控制; - 功能:修改、丢弃、新增请求或响应(用户可以编写配置代码);
Spider
:
- 解析
Download
返回的响应(Response); - 产生爬取项;
- 产生额外的爬取请求(Request)
Item Piplines
:
- 以流水线方式处理
Spider
产生的爬取项; - 由一组操作顺序组成,类似流水线,每个操作是一个
Item Pipline
类型; - 可能操作包括:清理、检验和查重爬取项中的HTML数据、将数据存储到数据库;
Spider Middleware
:
- 目的:对请求和爬取项的再处理;
- 功能:修改、丢弃、新增请求和爬取项;
使用步骤及数据类型
Scrapy
爬虫的使用步骤:
- 创建一个工程和
Spider
模板; - 编写
Spider
; - 编写
Item Pipeline
; - 优化配置策略;
Scrapy
爬虫的数据类型:
Request
类class scrapy.http.Request()
Request
对象表示一个HTTP请求;- 由
Spider
生成,由Downloader
执行;
常用的属性和方法:
属性或方法 | 说明 |
---|---|
.url |
Request 对应的请求URL地址 |
.method |
对应的请求方法,GET、POST等 |
.headers |
字典类型风格的请求头 |
.body |
请求内容主体,字符串类型 |
.meta |
用户添加的扩展信息,在Scrapy内部模块间传递信息使用 |
.copy() |
复制该请求 |
Response
类class scrapy.http.Reponse()
Reponse
对象表示一个HTTP响应;- 由
Downloader
生成,由Spider
处理;
常用的属性和方法:
属性或方法 | 说明 |
---|---|
.url |
Response 对应的请求URL地址 |
.status |
HTTP状态码,默认是200 |
.headers |
Response对应的头部信息 |
.body |
Response对应的内容信息,字符串类型 |
.flags |
一组标记 |
.request |
产生Response类型对应的Request对象 |
.copy() |
复制该请求 |
Item
类class scrapy.http.Item()
Item
对象表示一个从HTTP页面中提取的信息内容;- 由
Spider
生成,由Item Pipeline
处理; Item
类似字典类型,可以按照字典类型操作;
信息提取方法
Srapy
爬虫支持多种HTML信息提取方法:
Beautiful Soup
lxml
re
XPath Selector
CSS Selector
其它的方法前面都有介绍,这里我们简单说下CSS Selector
的基本使用:
1 | <HTML>.css('a::attr(href)').extract() |
Scrapy命令行
Scrapy
是为了持续运行设计的专业爬虫框架,提供操作的命令行。命令行格式如下:
1 | Scrapy <command> [optioins][args] |
Scrapy
常用命令
命令 | 说明 | 格式 |
---|---|---|
startproject |
创建一个工程 | scrapy startproject <name> [dir] |
genspider |
创建一个爬虫 | scrapy genspider [options] <name> <domian> |
settings |
获得爬虫配置信息 | scrapy settings [options] |
crwal |
运行一个爬虫 | scrapy crawl <spider> |
list |
列出工程中所有的爬虫 | scrapy list |
shell |
启动URL调试命令行 | scrapy shell [url] |
为什么Scrapy
采用命令行创建和运行爬虫?
- 命令行(不是图像界面)更容易自动化,适合脚步控制;
- 本质上,
Scrapy
是给程序员用的,功能(不是图形界面)更重要。
Scrapy
与 Request
比较
相同点:
- 两者都可以进行页面请求和爬取,python爬虫的两个重要技术路线;
- 两者可用性都好,文档丰富,入门简单;
- 两者都没有处理
js
,提交表单,应对验证码等功能(可扩展);
不同点:
request |
scrapy |
---|---|
页面及爬虫 | 网站级爬虫 |
功能库 | 框架 |
并发性考虑不足,性能较差 | 并发性好,性能较高 |
重点在于页面下载 | 重点在于爬虫结构 |
定制灵活 | 一般定制灵活,深度定制困难 |
上手十分简单 | 入门稍难 |
选用哪个技术路线开发爬虫
- 非常小的需求,
request
库; - 不太小的需求,
Scrapy
库; - 定制程度很高的需求(不考虑规模),自搭框架,
request
库 >Scrapy
库。
yield关键字
yield
生成器:
- 生成器是一个不断产生值的函数;
- 包含
yield
语句的函数是一个生成器; - 生成器每次产生一个值(
yield
语句),函数被冻结,被唤醒后再产生一个值。
为何要用生成器:
- 更节省存储空间;
- 响应更迅速;
- 使用更加灵活。
实例1: 一个简单的demo
演示HTML地址:http://python123.io/ws/demo.html
1). 生成工程文件scrapy startproject demo1
demo1/
:外层目录scrapy.cfg
:部署Scrapy
爬虫的配置文件demo1
Scrapy
:框架的用户自定义python代码__init__.py
:初始化脚本items.py
:Items
代码模板(继承类)middlewares.py
:Middlewares
代码模板(继承类)pipelines.py
:Pipilines
代码模板(继承类)settings.py
:Scrapy
爬虫的配置文件spiders/
:Spiders
代码模板目录(继承类)__init__.py
:初始化文件,无需修改__pycache__/
:缓存目录,无需修改
2). 在工程中产生一个Scrapy
爬虫
1 | ------------------------------------------------------------ |
spiders/
目录下生成了 demo.py
:
1 | # -*- coding: utf-8 -*- |
其中,parse()
用于处理响应,解析内容形成字典,发现新的URL爬取请求。
3). 配置产生的spider
爬虫
1 | # -*- coding: utf-8 -*- |
4). 运行爬虫获取网页
1 | scrapy crawl demo |
实例2:股票信息爬取
功能描述:
- 技术路线:
scrapy
; - 目标:获取上交所和深交所所有股票的名称和交易信息;
- 输出:保存到文件中。
数据网站的确定:
- 获取股票列表:http://quote.eastmoney.com/stocklist.html
- 获取个股信息
1 | #建立工程和Spider模板 |
配置并发连接选项优化爬取速度:
setting.py
文件
选项 | 说明 |
---|---|
CONCURRENT_REQUESTS |
Downloader最大并发请求下载数量,默认32 |
CONCURRENT_ITEMS |
Item Pipeline 最大并发ITEM处理数量,默认100 |
CONCURRENT_REQUESTS_PER_DOMAIN |
每个目标域名最大的并发请求数量,默认8 |
CONCURRENT_REQUESTS_PER_IP |
每个目标IP最大的并发请求数量,默认0,非0有效 |
Scrapy爬虫的地位
- Python语言最好的爬虫框架;
- 具备企业级专业爬虫的扩展性(7*24高可靠性);
- 千万级URL爬取管理与部署。
Scrapy爬虫足以支撑一般商业服务所需的爬虫能力。
- 普通价值:
- 基于linux服务器,7*24,稳定爬取输出;
- 商业级部署和应用(scrapyd-*);
- 千万级URL爬取、内容分析和存储;
- 高阶价值:
- 基于docker,虚拟化部署;
- 中间件扩展,增加调度和监控;
- 各种反爬取对抗技术。
写在最后
两种技术路线:
requests-bs4-re
scrapy(5+2结构)
由于这两种技术路线,还没法处理js
的表单提交、爬取周期、入库管理等相关功能,还需要配置pyantomJS
库来扩展来解析js
。
对于https://pypi.python.org
网站上以scrapy-
开头的文件都是用来完善scrapy库,也可以用来尝试。
“君子曰:学不可以已。积土成山,风雨兴焉。”–荀子《劝学》
生命不息,学习不止。
感谢嵩老师的课程。