目录清单
- 正则表达式提取数据
- 正则表达式案例操作
- Xpath提取数据
- Xpath案例操作
- BeautifulSoup4提取数据
- BeautifulSoup4案例操作
章节内容
1. 关于数据
爬虫程序,主要是运行在网络中进行数据采集的一种计算机程序,正常的一个爬虫采集数据的过程大致如下:
- 访问目标服务器
- 采集数据,获取访问url的数据
- 根据需要筛选数据
- 处理数据,存储到文件或者数据库,等待下一步进行数据分析或者数据展示
由于存在着不同的服务器和软件应用,所以爬虫获取到的数据就会出现各种不同的表现形式,但是总体来说还是有规律的,有规律就可以被掌握的
首先,关于爬虫处理的数据,一般分为两种数据
- 非结构化数据:数据的内容整体没有固定的格式和语法规范
- 结构化数据:数据的内容有固定的语法规范,按照一定的结构进行组织管理
这两种数据都分别表示什么样的数据呢,分别应该通过什么样的方式进行处理呢,这是爬虫在采集完数据之后,针对数据进行筛选必须要进行的操作
接下来,了解两种不同的数据的表现形式
非结构化数据
无格式字符串数据:用户名、邮箱、账号、电话号码、地址、电影名称、评分、评论、商品名称等等结构化数据
带有一定的格式的数据:HTML网页文档、XML网页文档、JSON等等
第三,对于不同的数据,进行有效数据筛选时,应该分别怎么进行操作呢
- 非结构化数据:由于没有任何固定的格式,只能根据字符出现的规律进行动态匹配的方式来完成数据的提取:正则表达式
- 结构化数据:由于数据本身存在一定的规律性,可以通过针对这些规律的分析工具进行数据的提取:正则表达式、Xpath、BeautifulSoup4、select、css等等
2. 正则表达式
正则表达式是一门单独的技术,在实际操作过程中由于它优雅的字符匹配特性,各种编程语言都陆续支持正则表达式的操作方式,Python中通过内建模块re进行正则表达式的处理,大致按照如下三个步骤进行数据的操作:
- 确定源数据:获取整体数据
- 按照目标数据定义正则表达式匹配规则
- 从整体数据中匹配符合要求的数据
正则表达式的处理,最核心的是先掌握正则表达式的语法和匹配规则,根据实际操作的不同需要,正则表达式定义了不同的数据匹配方式
匹配规则 | 规则描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n”匹配字符“n”。“\n”匹配一个换行符。串行“\”匹配“\”而“(”则匹配“(”。 |
^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo能匹配“z”以及“zoo”。等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。?等价于{0,1}。 |
{n} | n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 |
{n,} | n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。 |
{n,m} | m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。 |
. | 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.\n)”的模式 |
(pattern) | 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。 |
(?:pattern) | 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。 |
(?=pattern) | 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
(?<=pattern) | 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。 |
(? | 反向否定预查,与正向否定预查类拟,只是方向相反。例如“(? |
x|y | 匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。 |
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。 |
\cx | 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。 |
\d | 匹配一个数字字符。等价于[0-9]。 |
\D | 匹配一个非数字字符。等价于[^0-9]。 |
\f | 匹配一个换页符。等价于\x0c和\cL。 |
\n | 匹配一个换行符。等价于\x0a和\cJ。 |
\r | 匹配一个回车符。等价于\x0d和\cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于\x09和\cI。 |
\v | 匹配一个垂直制表符。等价于\x0b和\cK。 |
\w | 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。 |
\xn | 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。. |
\num | 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。 |
\nml | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。 |
3. python操作正则表达式
python内置了re模块,可以很方便快捷的操作正则表达式语法完成字符串的查询匹配操作行为,需要注意的是通过re操作正则表达式的两种表现形式
第一种方式主要是通过compile()函数根据给定的正则表达式编译生成正则匹配对象,通过正则匹配对象完成字符串的查询匹配操作过程
import re
# 定义正则表达式,通过compile()函数编译
pattern = re.compile('正则表达式')
# 核心操作函数
# 1.起始位置匹配一次:仅从指定的起始位置进行匹配(默认开头位置)
# 匹配成功返回匹配到的字符串,表示目标字符串是该字符串开头的
# 匹配失败返回None
value = pattern.match(string[, start[ , end]])
# 2.全文匹配一次:从指定的起始位置开始匹配(默认开头位置)
# 陆续对字符串中的所有字符进行匹配
# 匹配成功返回匹配到的字符串,表示目标字符串中包含该字符串
# 匹配失败返回None
value = pattern.search(string[, start[, end]])
# 3.全文匹配
# 从目标字符串中查询所有符合匹配规则的字符,并存储到一个列表中
# 匹配结束返回列表,包含匹配到的数据
# 没有匹配到数据返回空列表,否则返回包含所有匹配数据的列表
value_list = pattern.findall(string[, start[, end]])
# 4.全文匹配获取迭代器
# 从目标字符串中查询所有符合匹配规则的字符,并存储到一个迭代器中
value_iter = pattern.finditer(string[, start[, end]])
# 5.字符串切割:根据指定的正则表达式切割目标字符串并返回切割后的列表
value_list = pattern.split(string)
# 6.字符串替换:根据指定的匹配规则,将string中符合的字符串替换为value值,count是替换次数,默认全部替换
value_replace = pattern.sub(value, string[, count])
通过正则匹配到的值对象value,可以通过指定的函数输出匹配到的数据的信息
# 输出匹配到的数据
value.group()
# 输出匹配到的第一组数据
value.group(1)
# 输出匹配的第n组数据:前提条件是在正则表达式中使用圆括号进行了n次分组
value.group(n)
# 输出匹配数据的索引范围
value.span()
# 输出匹配的第n组数据的索引范围
value.span(n)
# 输出匹配的第n组数据的索引开始位置
value.start(n)
# 输出匹配的第n组数据的索引结束位置
value.end(n)
注意的是:在使用正则表达式时,贪婪模式和懒惰模式的操作行为可以精确的匹配数据
通常情况下,正则表达式模式是贪婪模式进行匹配的,如果需要精确匹配的情况下,在正常正则表达式后面添加一个?匹配符号即可!
# 定义目标字符串
>>> s = "helelo world"
# 编译正则匹配对象:这里我们只是想得到: lel
>>> pattern = re.compile('l*l')
# 进行数据匹配操作
>>> r = re.compile('l.*l')
# 展示数据
>>> r.search(s).group()
# 展示得到的数据,显然匹配的数据中包含了其他数据
'lelo worl'
# 那么,下面这个例子,貌似更加实际
>>> html = "i am divi am p
i am div too"
# 定义匹配规则,只是想匹配div中包含的数据
>>> pattern = re.compile(".*")
# 打印展示数据
>>> pattern.search(html).group()
# 显示的数据,明显包含了不需要的数据,这是贪婪模式
'i am divi am p
i am div'
# 重新定义
>>> html = "i am divi am p
i am div too"
# 修改过的正则表达式
>>> pattern = re.compile(".*?")
# 匹配得到数据
>>> pattern.search(html).group()
# 显示的数据,包含的数据,就是精确匹配到的数据
'i am div'
正则表达式案例操作:百度图片搜索下载
4. Xpath
Xpath原本是在可扩展标记语言XML中进行数据查询的一种描述语言,可以很方便的在XML文档中查询到具体的数据;后续再发展过程中,对于标记语言都有非常友好的支持,如超文本标记语言HTML。
在操作Xpath之前,首先需要了解一些基础的技术术语
下面是一段常见的HTML代码
文档标题
一级标题
标题
标题
标题
标题
内容
内容
内容
内容
- 根标签:在标记语言中,处在最外层的一个标签就是根标签,根标签有且仅有一个,在上述代码中就是跟标签
- 父标签:和子标签对应,内部包含了其他元素数据,该标签就是内部标签的父标签,如是的父标签,又是
的父标签,某些说法中,父标签的父标签..被称为上级标签或则先代标签或者先辈标签 - 子标签;和父标签对应,被包含的元素,就是外部元素的子标签,如是的子标签,
标签是的子标签, 是 的子标签;同样的子标签的子标签,也被称为后代标签
- 兄弟标签:两个或者多个处在相同级别的标签,有相同的父标签,如
和
是兄弟标签,和是兄弟标签,
中的两个
是兄弟标签等等
- Xpath描述语言的常见语法
和正则表达式相比较,Xpath使用最简单的语法操作完成数据的查询匹配操作
表达式 描述 nodename 选取此节点的所有子节点。 / 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 . 选取当前节点。 .. 选取当前节点的父节点。 @ 选取属性。 * 匹配任何元素节点。 @* 匹配任何属性节点。 node() 匹配任何类型的节点。 通过如下的方式直接操作上面的文档
路径表达式 结果 html 选取 html 元素的所有子节点。 /html 选取根元素 html。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! table/tr/td 选取属于 table 的子元素的所有 td 元素。 //div | //table 选取所有的div或者table节点 //table 选取所有 table 子元素,而不管它们在文档中的位置。 html//div 选择属于html元素的后代的所有div元素,而不管它们位于 html之下的什么位置。 //@href 选取名为href 的所有属性。 标签条件筛选查询匹配
路径表达式 结果 //table/tr[1] 选取属于table子元素的第一个 tr 元素。 //table/tr[last()] 选取属于 table 子元素的最后一个 tr 元素。 //table/tr[last()-1] 选取属于 table 子元素的倒数第二个 tr 元素。 //table/tr[position()<3] 选取最前面的两个属于 table 元素的子元素的tr元素。 //td[@width] 选取所有拥有名为 width 的属性的 td 元素。 //td[@width='100'] 选取所有 td 元素,且这些元素拥有属性width并且值为100。 //tr//td[span>10000] 选取tr元素的所有td子元素,并且其中的span 元素的值须大于10000。 同样,Xpath支持数据运算操作
运算符 描述 实例 返回值 + 加法 6 + 4 10 - 减法 6 - 4 2 * 乘法 6 * 4 24 div 除法 8 div 4 2 = 等于 price=9.80 如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。 != 不等于 price!=9.80 如果 price 是 9.90,则返回 true。如果 price 是9.80,则返回 false。 < 小于 price<9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 <= 小于或等于 price<=9.80 如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。 > 大于 price>9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。 >= 大于或等于 price>=9.80 如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。 or 或 price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。 and 与 price>9.00 and price<9.90 如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。 mod 计算除法的余数 5 mod 2 1
xpath在浏览器中进行测试时,可以给谷歌浏览器安装一个插件Xpath Helper插件;就可以直接在浏览器中通过xpath语法来完成对数据的匹配测试
测试通过的xpath语法,就可以直接在程序中使用了!
5.python操作Xpath
python第三方模块lxml可以对Xpath有友好的支持,lxml是C实现的一种高性能python用于HTML/XML的解析模块,可以通过Xpath语法在html文档数据中进行指定表达式数据的索引查询
- 简单etree操作
# -*- coding:utf-8 -*- from lxml import etree # 模拟得到爬虫数据 content = """
大牧 个人简介
姓名:某某某
住址:中国 乡下
座右铭:岂能尽如人意,但求无愧于心
- xpath操作
# -*- coding:utf-8 -*- from lxml import etree # 模拟得到爬虫数据 content = u"""
大牧 个人简介
姓名:某某某
住址:中国 乡下
座右铭:岂能尽如人意,但求无愧于心
案例操作:爬虫智联招聘中前10页的某个工作岗位名称、薪水、公司信息
6. BeautifulSoup4
BeautifulSoup也是一种非常优雅的专门用于进行HTML/XML数据解析的一种描述语言,可以很好的分析和筛选HTML/XML这样的标记文档中的指定规则数据
在数据筛选过程中其基础技术是通过封装HTML DOM树实现的一种DOM操作,通过加载网页文档对象的形式,从文档对象模型中获取目标数据
BeautifulSoup操作简单易于上手,在很多对于数据筛选性能要求并不是特别苛刻的项目中经常使用,目前市场流行的操作版本是BeautifulSoup4,经常称BS4
Xpath和BeautifulSoup
Xpath和BeautifulSoup都是基于DOM的一种操作模式
不同点在于加载文档对象模型DOM时出现的文档节点遍历查询操作过程,Xpath在进行遍历操作时针对描述语言指定的语法结构进行局部DOM对象树的遍历得到具体的数据,但是BS4在操作过程中,会将整个文档树进行加载然后进行查询匹配操作,使用过程中消耗资源较多,处理性能相对Xpath较低
那么为什么要用BS4呢?因为,它,足够简单!
描述语言 处理效率 上手程度 正则表达式 效率非常高 困难 Xpath 效率很高 正常 BS4 效率较高 简单
BS4本身是一种对描述语言进行封装的函数操作模块,通过提供面向对象的操作方式将文档对象中的各种节点、标签、属性、内容等等都封装成了python中对象的属性,在查询操作过程中,通过调用指定的函数直接进行数据 匹配检索操作,非常的简单非常的灵活。
一般BS4将HTML文档对象会转换成如下四种类型组合的文档树
- Tag:标签对象
- NavigableString:字符内容操作对象
- BeautifulSoup:文档对象
- Comment:特殊类型的NavigableString
说道这里,其实都是太多的理论性语法,BS4不同于正则和Xpath,没有什么基础语法结构,它封装的对象以及对象的属性操作,才是BS4不同凡响的核心价值
let's 上干货
7. python操作BeautifulSoup4
python中对于BeautifulSoup的支持,通过安装第三方模块来发挥它最好的操作
$ pip install beautifulsoup4
- 入门第一弹:了解BeautifulSoup4
# coding:utf-8 # 引入解析模块BS4 from bs4 import BeautifulSoup # 从文件中加载html网页,指定HTML解析器使用lxml # 默认不指定的情况下,BS4会自动匹配当前系统中最优先的解析器 soup = BeautifulSoup(open("index.html"), "lxml") # 如果是爬虫获取到的字符数据,直接交给BS4就OK拉 # soup = BeatufulSoup(spider_content, "lxml") # 打印BeautifulSoup文档对象,得到的是文档树内容 print(soup) # 打印类型:
print(type(soup)) - 入门第二弹:操作标签、属性、内容
# coding:utf-8 from bs4 import BeautifulSoup # 得到构建的文档对象 soup = BeautifulSoup(open("index.html"), "lxml") # Tag操作 # 1. 获取标签 print(soup.title) #
文章标题 print(soup.p) #姓名:大牧
# 只返回第一个匹配到的标签对象 print(soup.span) # 大牧 # 2.获取标签的属性 print(soup.p.attrs) # {}:得到属性和值的字典 print(soup.span.attrs) # {'id': 'name'}:得到属性和值的字典 print(soup.span['id']) # name:得到指定属性的值 soup.span['id'] = "real_name" print(soup.span['id']) # real_name : 可以方便的在BS4中直接对文档进行修改 # 3. 获取标签的内容 print(soup.head.string) # 文章标题:如果标签中只有一个子标签~返回子标签中的文本内容 print(soup.p.string) # None:如果标签中有多个子标签,返回None print(soup.span.string) # 大牧:直接返回包含的文本内容- 入门第三弹:操作子节点
# coding:utf-8 # 引入BS4操作模块 from bs4 import BeautifulSoup # 加载网页文档,构建文档对象 soup = BeautifulSoup(open("index.html"), "lxml") print(dir(soup)) print(soup.contents)# 得到文档对象中所有子节点 print(soup.div.contents)# 得到匹配到的第一个div的子节点列表 print(soup.div.children)# 得到匹配到的第一个div的子节点列表迭代器 # for e1 in soup.div.children: # print("-->", e1) print(soup.div.descendants)# 得到匹配到的第一个div的子节点迭代器,所有后代节点单独一个一个列出 # for e2 in soup.div.descendants: # print("==>", e2)
- 入门第四弹: 面向对象的DOM匹配
# coding:utf-8 # 引入BS4模块 from bs4 import BeautifulSoup # 加载文档对象 soup = BeautifulSoup(open("../index.html"), "lxml") # DOM文档树查询 # 核心函数~请对比javasript dom结构了解它的方法 # 如:findAllPrevious()/findAllNext()/findAll()/findPrevious()/findNext()等等 # findAll()为例 # 1. 查询指定的字符串 res1 = soup.findAll("p")# 查询所有包含p字符的标签 print(res1) # 2. 正则表达式 import re res2 = soup.findAll(re.compile(r"d+"))# 查询所有包含d字符的标签 print(res2) # 3. 列表:选择 res3 = soup.findAll(["div", "h1"])# 查询所有的div或者h1标签 print(res3) # 4. 关键字参数 res4 = soup.findAll(id="name")# 查询属性为id="name"的标签 print(res4) # 5. 内容匹配 res5 = soup.findAll(text=u"男")# 直接匹配内容中的字符,必须保证精确匹配 print(res5) res6 = soup.findAll(text=[u"文章标题", u"大牧"])# 查询包含精确内容的所有的标签 print(res6) res7 = soup.findAll(text=re.compile(u"大+"))# 通过正则表达式进行模糊匹配 print(res7)
- 入门第五弹: 又见CSS
# coding:utf-8 # 引入BS模块 from bs4 import BeautifulSoup # 加载网页构建文档对象 soup = BeautifulSoup(open("index.html"), "lxml") # 1. CSS 标签选择器:根据标签名称查询标签对象 res1 = soup.select("span") print(res1) # 2. CSS ID选择器:根据ID查询标签对象 res2 = soup.select("#gender") print(res2) # 3. CSS 类选择器:根据class属性查询标签对象 res3 = soup.select(".intro") print(res3) # 4. CSS 属性选择器 res41 = soup.select("span[id]") print(res41) res42 = soup.select("span[id='gender']") print(res42) # 5. CSS 包含选择器 res5 = soup.select("p span#name") print(res5) # 6. 得到标签内容 res6 = soup.select("p > span.intro") print(res6[0].string) print(res6[0].getText())
- 兄弟标签:两个或者多个处在相同级别的标签,有相同的父标签,如