是的,爬虫就是为了获取数据。在获取的数据中,会有很多的冗余信息,需要在获取的数据中提取所需要的有用信息。进而联想到数据的匹配:正则表达式。接下来重点介绍的是 Python 中的两个提取数据的两个框架 re 与 XPath。
一、正则表达式正则表达式是通用的,不区分任何的语言。以下是一些比较常用的通配符:
image.png
二、re在 re 中有三个比较重要的函数,介绍如下:
findall: 匹配所有符合规律的内容,返回包含结果的列表
Search:匹配并提取第一个符合规律的内容,返回一个正则表达式对象(object)
Sub :替换符合规律的内容,返回替换后的值
接下来是将一些比较常用的语法通过以上三个函数都使用以下。当然了, 在使用之前先导入 re。
import re2.1 关于点、星号与问号的使用
# 点的使用def dotFunc():
# 定义一个字符串
value = 'Hello, CoderHG. My name is CoderOC.'
# 匹配名字
names = re.findall('Coder..', value) # 打印
print(names) # 结果 ['CoderHG', 'CoderOC']
# 依次打印
for name in names:
print(name)# 星号的使用def starFunc():
value = 'Hello, CoderHG.'
name = re.findall('Coder*', value)
print(name) # 结果 ['Coder']# 问号的使用def questionFunc():
value = 'Hello, CoderHG.'
name = re.findall('Coder?', value)
print(name) # 结果 ['Coder']2.2 贪心算法主要是 .* 匹配, 代码如下:
### 2.2 贪心算法def tanxinFunc():
secret_code = 'hadkfalifexxIxxfasdjifja134xxlovexx23345sdfxxyouxx8dfse'
b = re.findall('xx.*xx',secret_code)
print(b) # 打印结果 ['xxIxxfasdjifja134xxlovexx23345sdfxxyouxx']匹配的是尽量多的内容。
2.3 非贪心算法主要是匹配 .*?匹配, 代码如下:
# 非贪心算法def notTanxinFunc():
secret_code = 'hadkfalifexxIxxfasdjifja134xxlovexx23345sdfxxyouxx8dfse'
b = re.findall('xx.*?xx',secret_code)
print(b) # 打印结果 ['xxIxx', 'xxlovexx', 'xxyouxx']尽量的少,尽量的细致的去匹配
2.4 括号+非贪心算法# 括号+非贪心算法def kuohaonotTanxinFunc():
secret_code = 'hadkfalifexxIxxfasdjifja134xxlovexx23345sdfxxyouxx8dfse'
b = re.findall('xx(.*?)xx', secret_code)
print(b) # 打印结果 ['I', 'love', 'you']
for text in b:
print(text)2.5 re.S 参数的作用def huanhangFunc():
s = '''sdfxxhe
llo
xxfsdfxxworldxxasdf'''
# 不带 re.S 参数
d = re.findall('xx(.*?)xx', s)
print(d) # 打印结果: ['fsdf']
# 带有 re.S 参数
d_reS = re.findall('xx(.*?)xx',s, re.S)
print(d_reS) # 打印结果: ['hen llon ', 'world']2.6 findall 与 search 结合使用加上 re.S 能匹配出带有换行的字符。
def findallAndSearchFunc():
s2 = 'asdfxxIxx123xxlovexxdfd'
result = re.search('xx(.*?)xx123xx(.*?)xx',s2)
print(result) # 打印结果 对象: <_sre.SRE_Match object; span=(4, 20), match='xxIxx123xxlovexx'>
f = result.group(2)
print(f) # 打印结果 : love
result = re.findall('xx(.*?)xx123xx(.*?)xx',s2)
print(result) # 打印结果 数组套元组: [('I', 'love')]
f = result[0][1]
print(f) # 打印结果 : love2.7 sub 的使用# sub 的使用, 替换字符串def subFunc():
s = '我很想对她说: I what You.'
f = re.sub('I (.*?) You.', 'I love You.', s)
print(f) # 打印结果: 我很想对她说: I love You.三、XPath3.1 安装 lxml通过 pip list 指令查看是否已经安装 list。 如果没有安装,执行 pip install lxml 即可。
这个东西与其他的库有所不同, 说的是 XPath, 安装的是 lxlml。在使用的时候, 直接导入:
from lxml import etree
3.2 使用格式3.2.1 套路温馨提示:按照以上的步骤如果import 的时候出错, 那么到设置中手动安装一下。
3.2.2 规律Selector = etree.HTML(网页源代码)
Selector.xpath(一段神奇的符号)
树状结构
逐层展开
逐层定位
4.寻找独立节点
// 定位根节点
/ 往下层寻找
提取文本内容:/text()
提取属性内容: /@xxxx
这里有一个简单的 HTML 文本, 如下:
测试-常规用法
- 这是第一条信息
- 这是第二条信息
- 这是第三条信息
- 不需要的信息1
- 不需要的信息2
- 不需要的信息3
现在想要提取 id = useful 中的 ui 标签中的内容, 以及 获取 a 标签中的连接,如果使用 re 的话, 那就不好办了, 尤其是提取 id = useful 中的 ui 标签中的内容,因为还有一个与之类似的 id=userless 的标签。
使用 XPath 的话, 就显得容易了, 代码如下:
# XPath 的相关用法def xpatHTML():
# 获取一个与 XPath 相关的对象
selector = etree.HTML(html) # 提取文本 是数组
contents = selector.xpath('//ul[@id="useful"]/li/text()') # content = selector.xpath('//*[@id="useful"]/li/text()')
# 打印获取的内容
for content in contents:
print(content) # 打印结果:
# 这是第一条信息
# 这是第二条信息
# 这是第三条信息
# 提取属性
links = selector.xpath('//a/@href') for link in links:
print(link) # 打印结果:
# http://jikexueyuan.com
# http://jikexueyuan.com/course/3.3.2 特殊用法一(以相同的字符开头)现在有以下一段 html 的文本内容:
需要的内容1 需要的内容2 需要的内容3
需要提取 id=‘test*’ 中的内容, 那么久需要使用到的的语法是这样的:
starts-with(@属性名称, 属性字符相同部分)
代码可见:
# 以相同的字符开头def startwithFunc():
print('以相同的字符开头') # 获取一个与 XPath 相关的对象
selector = etree.HTML(html) # 提取文本 是数组
contents = selector.xpath('//div[starts-with(@id,"test")]/text()') # 打印
for content in contents:
print(content) # 打印结果:
# 需要的内容1
# 需要的内容2
# 需要的内容33.3.3 特殊用法二(标签套标签)有一个如下的 HTML 文本:
我左青龙, 右白虎,
- 上朱雀,
- 下玄武。
现在想要提取这样的内容:我左青龙,右白虎,上朱雀,下玄武。老牛在当中,龙头在胸口。
那么就要使用 string(.), 实现代码如下:
# 标签套标签def divdivFunc():
print('标签套标签') # 获取一个与 XPath 相关的对象
selector = etree.HTML(html) # 提取文本
contents = selector.xpath('//div[@id="test3"]/text()') # 打印
print(contents) # 打印结果: ['n 我左青龙,n ', 'n 龙头在胸口。n ']
# 这里的结果没有打印出标签中的标签的内容
# 还需要这么做
datas = selector.xpath('//div[@id="test3"]') # 获取数组中的第一个元素
data = datas[0] # 获取内容
info = data.xpath('string(.)') # 字符串替换 'n' -> ''
content = info.replace('n', '') # 字符串替换 ' ' -> ''
content = content.replace(' ', '')
print(content) # 打印结果: 我左青龙,右白虎,上朱雀,下玄武。老牛在当中,龙头在胸口。小总结正在表达式能处理很多的问题了,但是有的时候也会遇到以上 XPath 中介绍的数据结构, 如果还是使用正则表达式的话, 就有点不简单的。以上可以看出使用 XPath 还是很简单的。
四、Python 的并行化以一个异步获取贴吧数据为例:
#-*-coding:utf8-*-# as 的语法是将 Pool 替换成 ThreadPool 来使用from multiprocessing.dummy import Pool as ThreadPoolimport multiprocessingimport requestsimport time# 存储所有的 urlurls = []# 获取所有的 urldef getURLs(): for i in range(1, 21): # 生成连接 newpage = 'http://tieba.baidu.com/p/3522395718?pn=' + str(i) # 添加到 urls 列表中 urls.append(newpage)# 爬去数据def getsource(url): requests.get(url) print(url)# 单线程获取数据def sigleFunc(): startTime = time.time() for url in urls: getsource(url) endTime = time.time() print(u'单线程耗时' + str(endTime-startTime))# 并行处理def poolFunc(): count = multiprocessing.cpu_count(); print(count) startTime = time.time() pool = ThreadPool(count) results = pool.map(getsource, urls) pool.close() pool.join() endTime = time.time() print(u'耗时' + str(endTime - startTime))if __name__ == '__main__': # 获取所有的 URL getURLs() # 单线程 sigleFunc() # 多线程(并行) poolFunc()
Log日志输入如下:
http://tieba.baidu.com/p/3522395718?pn=1http://tieba.baidu.com/p/3522395718?pn=2http://tieba.baidu.com/p/3522395718?pn=3http://tieba.baidu.com/p/3522395718?pn=4http://tieba.baidu.com/p/3522395718?pn=5http://tieba.baidu.com/p/3522395718?pn=6http://tieba.baidu.com/p/3522395718?pn=7http://tieba.baidu.com/p/3522395718?pn=8http://tieba.baidu.com/p/3522395718?pn=9http://tieba.baidu.com/p/3522395718?pn=10http://tieba.baidu.com/p/3522395718?pn=11http://tieba.baidu.com/p/3522395718?pn=12http://tieba.baidu.com/p/3522395718?pn=13http://tieba.baidu.com/p/3522395718?pn=14http://tieba.baidu.com/p/3522395718?pn=15http://tieba.baidu.com/p/3522395718?pn=16http://tieba.baidu.com/p/3522395718?pn=17http://tieba.baidu.com/p/3522395718?pn=18http://tieba.baidu.com/p/3522395718?pn=19http://tieba.baidu.com/p/3522395718?pn=20单线程耗时18.2060580253601078http://tieba.baidu.com/p/3522395718?pn=3http://tieba.baidu.com/p/3522395718?pn=8http://tieba.baidu.com/p/3522395718?pn=2http://tieba.baidu.com/p/3522395718?pn=5http://tieba.baidu.com/p/3522395718?pn=6http://tieba.baidu.com/p/3522395718?pn=7http://tieba.baidu.com/p/3522395718?pn=4http://tieba.baidu.com/p/3522395718?pn=1http://tieba.baidu.com/p/3522395718?pn=14http://tieba.baidu.com/p/3522395718?pn=13http://tieba.baidu.com/p/3522395718?pn=10http://tieba.baidu.com/p/3522395718?pn=12http://tieba.baidu.com/p/3522395718?pn=9http://tieba.baidu.com/p/3522395718?pn=15http://tieba.baidu.com/p/3522395718?pn=11http://tieba.baidu.com/p/3522395718?pn=16http://tieba.baidu.com/p/3522395718?pn=19http://tieba.baidu.com/p/3522395718?pn=18http://tieba.baidu.com/p/3522395718?pn=20http://tieba.baidu.com/p/3522395718?pn=17耗时2.678765058517456
结论: 使用并行处理, 可以节省不少的时间。
作者:CoderHG
链接:https://www.jianshu.com/p/3b5380c2aae8



