是的,爬虫就是为了获取数据。在获取的数据中,会有很多的冗余信息,需要在获取的数据中提取所需要的有用信息。进而联想到数据的匹配:正则表达式。接下来重点介绍的是 Python 中的两个提取数据的两个框架 re 与 XPath。
一、正则表达式
正则表达式是通用的,不区分任何的语言。以下是一些比较常用的通配符:
二、re
在 re 中有三个比较重要的函数,介绍如下:
- findall: 匹配所有符合规律的内容,返回包含结果的列表
- Search:匹配并提取第一个符合规律的内容,返回一个正则表达式对象(object)
- Sub :替换符合规律的内容,返回替换后的值
接下来是将一些比较常用的语法通过以上三个函数都使用以下。当然了, 在使用之前先导入 re。
import re
2.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)
# 打印结果: ['he\n llo\n ', 'world']
加上 re.S 能匹配出带有换行的字符。
2.6 findall 与 search 结合使用
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)
# 打印结果 : love
2.7 sub 的使用
# sub 的使用, 替换字符串
def subFunc():
s = '我很想对她说: I what You.'
f = re.sub('I (.*?) You.', 'I love You.', s)
print(f)
# 打印结果: 我很想对她说: I love You.
三、XPath
3.1 安装 lxml
通过 pip list 指令查看是否已经安装 list。 如果没有安装,执行 pip install lxml 即可。
这个东西与其他的库有所不同, 说的是 XPath, 安装的是 lxlml。在使用的时候, 直接导入:
from lxml import etree
温馨提示:按照以上的步骤如果import 的时候出错, 那么到设置中手动安装一下。
3.2 使用格式
3.2.1 套路
Selector = etree.HTML(网页源代码)
Selector.xpath(一段神奇的符号)
3.2.2 规律
-
- 树状结构
-
- 逐层展开
-
- 逐层定位
- 4.寻找独立节点
3.2.3 语法
-
- // 定位根节点
-
- / 往下层寻找
-
- 提取文本内容:/text()
-
- 提取属性内容: /@xxxx
3.3 实例
3.3.1 常规用法
这里有一个简单的 HTML 文本, 如下:
测试-常规用法
现在想要提取 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
# 需要的内容3
3.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 ThreadPool
import multiprocessing
import requests
import time
# 存储所有的 url
urls = []
# 获取所有的 url
def 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=1
http://tieba.baidu.com/p/3522395718?pn=2
http://tieba.baidu.com/p/3522395718?pn=3
http://tieba.baidu.com/p/3522395718?pn=4
http://tieba.baidu.com/p/3522395718?pn=5
http://tieba.baidu.com/p/3522395718?pn=6
http://tieba.baidu.com/p/3522395718?pn=7
http://tieba.baidu.com/p/3522395718?pn=8
http://tieba.baidu.com/p/3522395718?pn=9
http://tieba.baidu.com/p/3522395718?pn=10
http://tieba.baidu.com/p/3522395718?pn=11
http://tieba.baidu.com/p/3522395718?pn=12
http://tieba.baidu.com/p/3522395718?pn=13
http://tieba.baidu.com/p/3522395718?pn=14
http://tieba.baidu.com/p/3522395718?pn=15
http://tieba.baidu.com/p/3522395718?pn=16
http://tieba.baidu.com/p/3522395718?pn=17
http://tieba.baidu.com/p/3522395718?pn=18
http://tieba.baidu.com/p/3522395718?pn=19
http://tieba.baidu.com/p/3522395718?pn=20
单线程耗时18.206058025360107
8
http://tieba.baidu.com/p/3522395718?pn=3
http://tieba.baidu.com/p/3522395718?pn=8
http://tieba.baidu.com/p/3522395718?pn=2
http://tieba.baidu.com/p/3522395718?pn=5
http://tieba.baidu.com/p/3522395718?pn=6
http://tieba.baidu.com/p/3522395718?pn=7
http://tieba.baidu.com/p/3522395718?pn=4
http://tieba.baidu.com/p/3522395718?pn=1
http://tieba.baidu.com/p/3522395718?pn=14
http://tieba.baidu.com/p/3522395718?pn=13
http://tieba.baidu.com/p/3522395718?pn=10
http://tieba.baidu.com/p/3522395718?pn=12
http://tieba.baidu.com/p/3522395718?pn=9
http://tieba.baidu.com/p/3522395718?pn=15
http://tieba.baidu.com/p/3522395718?pn=11
http://tieba.baidu.com/p/3522395718?pn=16
http://tieba.baidu.com/p/3522395718?pn=19
http://tieba.baidu.com/p/3522395718?pn=18
http://tieba.baidu.com/p/3522395718?pn=20
http://tieba.baidu.com/p/3522395718?pn=17
耗时2.678765058517456
结论: 使用并行处理, 可以节省不少的时间。
温馨提示:本篇介绍,来自于 极客学院 的学习记录。感谢极客学院课程!