美文网首页程序员码农的世界
Python奇技淫巧之利用协程加速百度百科词条爬虫

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

作者: b4a0155c6514 | 来源:发表于2019-01-14 10:42 被阅读3次

前一个系列文章主要利用百度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)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狂转,风扇的声音就跟开飞机一样!

image

完结,撒花,ye~

image

相关文章

网友评论

    本文标题:Python奇技淫巧之利用协程加速百度百科词条爬虫

    本文链接:https://www.haomeiwen.com/subject/iikddqtx.html