前一个系列文章主要利用百度AI的Python SDK进行图像识别、语音合成、语音识别,实现了一些有趣的小案例,实际上百度AI的功能远不止这些,更多高逼格的东西例如NLP、舆情分析、知识图谱等有待大家进一步发掘。
学习Python中有不明白推荐加入交流群
号:960410445
群里有志同道合的小伙伴,互帮互助,
群里有不错的视频学习教程和PDF!
这次我们来看看如何利用协程来加速百度百科词条爬虫。工欲善其事,必先利其器,相信学过其他传统多线程编程语言例如Java、go语言等的朋友对协程这个概念可能不太熟悉,实际上协程是也并非Python独有,它本质上是对线程的一种封装,那么Python中既然有多线程,为什么还要协程呢?原因就在于多线程的并发控制是一件很复杂的事情,基于锁机制的并发控制方法,一不小心就会产生线程冲突,导致死锁等情况的发生,为了降低多线程编程的门槛,让开发者专注于业务而从技术细节脱身出来,就有了协程。一旦开启它会在多个协程之间自动切换,并且在多个协程写入同一个文件时,不会产生写冲突,显然这是多线程所不具备的优良特性。那么协程既然有这么些优点,是不是说明我们可以抛弃进程和线程呢,也不尽然,存在即合理。在Python爬虫中,进程拥有的资源多,适合计算密集型的工作,例如分词、数据处理等,线程拥有的资源少,适合IO密集型的工作,例如发送网络请求读取网页内容等可能需要等待较长时间的工作。在分布式爬虫中,可能需要它们相互嵌套组合,一个分布式程序开启多个进程,一个进程可以包含多个线程,一个线程又可以包含多个协程,这样就能极大的加快爬虫速度。
百度百科词条不同于我们之前常见的爬虫,在一个词条详情页面可能又包含很多其他词条,点击又会进入其他词条的详情页,并且它们的url并没有某种固定的关系。因此只能从某个词条详情页开始爬取,取出该页面的所有词条,再采用深度优先遍历或者广度优先遍历的方法,依次递归爬取。我们采用广度优先遍历,这个过程需要两个队列,爬取的词条名称以及词条信息简介需要放入一个队列,并定期保存到磁盘。词条的链接需要放入一个队列,保证广度优先,那么如何解决重复爬取的问题呢,也就是说两个词条的详情页面,可能互相包含对方的词条链接,我们可以创建一个set,把已经爬取过的url放入其中,保证它的唯一性。
完整代码如下:
1"""
2@author: Kevin Wong
3@function: 百度百科爬虫
4@time: 2018/12/20 21:39
5"""
6import gevent
7import gevent.monkey
8gevent.monkey.patch_all() # 协程自动切换
9import requests, re, time
10from bs4 import BeautifulSoup
11from queue import Queue
12
13# 获取当前页面所有词条链接
14def getUrlList(page_content):
15 # 创建集合方便去重
16 url_list=set()
17 soup = BeautifulSoup(page_content, "html.parser")
18 links=soup.find_all("a", href=re.compile(r"/item/.*"))
19 for link in links:
20 url="https://baike.baidu.com"
21 # 拼接url
22 url+=link["href"]
23 url_list.add(url)
24 return url_list
25
26# 获取词条释义
27def getItemMeaning(page_content):
28 soup = BeautifulSoup(page_content,"html.parser")
29 summary = soup.find_all("div",class_ = "lemma-summary")
30 if len(summary)!= 0:
31 return summary[0].get_text()
32 else:
33 return ''
34
35# 获取词条名称以及所属分类的方法
36def getItemInfo(page_content):
37 try:
38 soup = BeautifulSoup(page_content, "html.parser") # 解析页面内容
39 item_name = soup.select('body > div.body-wrapper > div.content-wrapper > div > div.main-content > dl.lemmaWgt-lemmaTitle.lemmaWgt-lemmaTitle- > dd > h1')
40 item_category = soup.select('body > div.body-wrapper > div.content-wrapper > div > div.main-content > dl.lemmaWgt-lemmaTitle.lemmaWgt-lemmaTitle- > dd > h2')
41 if item_name is not None and item_category is not None:
42 if len(item_name) != 0 and len(item_category) != 0:
43 return item_name[0].text + '\t' + item_category[0].text
44 elif len(item_name) != 0 and len(item_category) == 0:
45 return item_name[0].text
46 else:
47 return ''
48 except:
49 return ''
50# 获取页面内容的方法
51def getPageContent(url):
52 user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' # 模拟浏览器
53 headers = {'User-Agent': user_agent}
54 try:
55 response = requests.get(url, headers=headers)
56 if response.status_code == 200:
57 response.encoding = "utf-8" # 设置编码
58 return response.text
59 else:
60 return ''
61 except:
62 return ''
63
64# 保存词条信息的队列
65def saveItem():
66 global item_queue
67 item_file = open("item.txt", "wb")
68 i = 0
69 while True:
70 i += 1
71 # 每隔5秒执行一次保存
72 time.sleep(5)
73 while not item_queue.empty():
74 data = item_queue.get()
75 item_file.write((data + "\r\n").encode("utf-8", "ignore"))
76 item_file.flush()
77 yield i
78 item_file.close()
79
80# 获取页面词条信息 并将页面的链接入队
81def getItem(url):
82 print("抓取", url)
83 global item_queue
84 global url_queue
85 # 获得当前页面内容
86 page_content = getPageContent(url)
87 # 获取词条信息
88 item_info = getItemInfo(page_content)
89 # 获取词条释义
90 item_meaning = getItemMeaning(page_content)
91 # 获取当前页面的所有词条链接 返回的是set
92 item_urls = getUrlList(page_content)
93 # 将词条信息及词条释义入队
94 item_queue.put(str(item_info or '') + '\r\n' + str(item_meaning or ''))
95
96 # 将当前页面的所有词条链接压入队列
97 if len(item_urls)!=0:
98 for myurl in item_urls:
99 url_queue.put(myurl)
100
101# 利用广度优先遍历所有词条
102def BFS(url):
103 global item_queue
104 global url_queue
105 # 将当前url入队
106 url_queue.put(url)
107 # 保存词条信息 此处返回的是一个迭代器
108 save_item = saveItem()
109 while True:
110 # 每隔5秒 抓取100个url
111 time.sleep(5)
112 url_list = []
113 for i in range(100):
114 # 只要链接队列不为空 则抓取一个url放入url_list中
115 if not url_queue.empty():
116 url_list.append(url_queue.get())
117 # 根据url_list中url的个数,新建一个协程组,自动切换
118 task_list = []
119 for url in url_list:
120 task_list.append(gevent.spawn(getItem, url))
121 gevent.joinall(task_list)
122 next(save_item)
123 print("save")
124def main():
125 url = "https://baike.baidu.com/item/Python/407313"
126 # 基于广度优先进行遍历
127 BFS(url)
128
129if __name__ == '__main__':
130 # 字段队列
131 item_queue = Queue()
132 # 链接队列
133 url_queue = Queue()
134 main()
这里从Python这个词条开始爬取,运行之后,如果不手动关闭程序,它会不停爬取词条名称以及词条的简介,并且源源不断的保存到item.txt这个文件中。不得不说,引入协程后,爬虫的速度完全上升了一个量级,搞的我的电脑CPU狂转,风扇的声音就跟开飞机一样!
完结,撒花,ye~