Python奇技淫巧之利用协程加速百度百科词条爬虫!

前一个系列文章主要利用百度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 + '	' + 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 + "
").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 '') + '
' + str(item_meaning or '')) 95 96 # 将当前页面的所有词条链接压入队列 97 if len(item_urls)!=0: 98 for myurl in item_urls: 99 url_queue.put(myurl)100101# 利用广度优先遍历所有词条102def BFS(url):103 global item_queue104 global url_queue105 # 将当前url入队106 url_queue.put(url)107 # 保存词条信息 此处返回的是一个迭代器108 save_item = saveItem()109 while True:110 # 每隔5秒 抓取100个url111 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)128129if __name__ == '__main__':130 # 字段队列131 item_queue = Queue()132 # 链接队列133 url_queue = Queue()134 main()

这里从Python这个词条开始爬取,运行之后,如果不手动关闭程序,它会不停爬取词条名称以及词条的简介,并且源源不断的保存到item.txt这个文件中。不得不说,引入协程后,爬虫的速度完全上升了一个量级,搞的我的电脑CPU狂转,风扇的声音就跟开飞机一样!

《Python奇技淫巧之利用协程加速百度百科词条爬虫!》
《Python奇技淫巧之利用协程加速百度百科词条爬虫!》

image

完结,撒花,ye~

《Python奇技淫巧之利用协程加速百度百科词条爬虫!》

    原文作者:萌新程序员
    原文地址: https://zhuanlan.zhihu.com/p/55238880
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞