Python 爬虫找到数据了 re & XPath & requests & Pool
2018.06.16 23:18 88浏览
字号
是的,爬虫就是为了获取数据。在获取的数据中,会有很多的冗余信息,需要在获取的数据中提取所需要的有用信息。进而联想到数据的匹配:正则表达式。接下来重点介绍的是 Python 中的两个提取数据的两个框架 re 与 XPath。
一、正则表达式
正则表达式是通用的,不区分任何的语言。以下是一些比较常用的通配符:
image.png
二、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 文本, 如下:
<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title>测试-常规用法</title></head><body><div id="content"> <ul id="useful"> <li>这是第一条信息</li> <li>这是第二条信息</li> <li>这是第三条信息</li> </ul> <ul id="useless"> <li>不需要的信息1</li> <li>不需要的信息2</li> <li>不需要的信息3</li> </ul> <div id="url"> <a href="http://jikexueyuan.com">极客学院</a> <a href="http://jikexueyuan.com/course/" title="极客学院课程库">点我打开课程库</a> </div></div></body></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 的文本内容:
<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title></head><body> <div id="test-1">需要的内容1</div> <div id="test-2">需要的内容2</div> <div id="testfault">需要的内容3</div></body></html>
需要提取 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 文本:
<!DOCTYPE html><html><head lang="en"> <meta charset="UTF-8"> <title></title></head><body> <div id="test3"> 我左青龙, <span id="tiger"> 右白虎, <ul>上朱雀, <li>下玄武。</li> </ul> 老牛在当中, </span> 龙头在胸口。 </div></body></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
结论: 使用并行处理, 可以节省不少的时间。