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

版权声明:禁止转载至其它平台,转载至博客需带上此文链接。 https://blog.csdn.net/qq_41841569/article/details/86470832

前一个系列文章主要利用百度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~

猜你喜欢

转载自blog.csdn.net/qq_41841569/article/details/86470832