爬虫实践 | 维基百科深度优先与广度优先的开展

时间:2022-06-25
本文章向大家介绍爬虫实践 | 维基百科深度优先与广度优先的开展,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

维基百科爬虫实战中,将采用的技术如下:

  1. 爬取网页:静态网页
  2. 解析网页;正则表达式
  3. 存储数据:txt文本存储
  4. 扩展:深度优先的递归爬虫和广度优先的多线程爬虫

1.项目描述

1.1项目目标

本爬虫目标为爬取维基百科上词条的链接,注意一点,在运行爬虫时注意不要过快,过频密的请求爬取维基百科网页,以免对服务器产生大量负荷。

1.2项目描述

如果需要爬取一个网站上的所有链接,采取什么方法比较好呢?

可以找到该网站上的一个网页,如主页,获取主页的内容,分析网页内容并找到网页上所有本站链接,然后爬取这些获得的链接,再分析这些链接网页上的内容,找到上面所有本站链接,并不断重复直到没有新的链接为止。

本次用于实践一个维基百科爬虫,不需要全站爬取,所以设定爬取深度为2,如果有兴趣,你们可以爬取更大的深度。

1.3深度优先和广度优先

如何把整个网站所有网页爬取一遍呢?这里说到两种算法:基于深度优先饿遍历和基于广度优先的遍历。

深度优先的遍历:可以描述为“不撞南墙不回头”,具体一点就是首先访问第一个邻接节点,然后以这个被访问的邻接节点作为初始节点,访问它的第一个邻接节点。访问策略是优先往纵向挖掘深入,直到到达指定的深度或该节点不存在邻接节点,才回掉头访问第二条路。

就像维基百科为例,假设现在的深度为3,深度优先遍历,如下:

基于深度优先的爬虫路径为:1->2->6->7->8->3->4->5

广度优先的遍历:可以描述为“一层一层地剥开我的心”,具体点就是,从某个顶点出发,首先访问这个顶点,然后找出这个节点的所有未被访问的邻接节点访问完后再访问这些节点中第一个邻接节点的所有节点,重复此方法,直到所有节点都被访问完为止。访问策略采用先访问完一个深度的所有节点,再访问更深一层的所有节点,并采用FIFO(先进先出)的策略。

基于广度优先的爬虫路径为:1->2->3->4->5->6->7->8

2 网站分析

维基百科首页地址: https://en.wikipedia.org/wiki/Wikipedia ,也就是Wikipedia词条的页面。

取出本页面所有链接,代码如下:

import requests
from bs4 import BeautifulSoup
import time


headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
r = requests.get("https://en.wikipedia.org/wiki/Wikipedia", headers= headers)
html = r.text
bsObj = BeautifulSoup(html, "lxml")


for link in bsObj.findAll("a"):
    if 'href' in link.attrs:
        print (link.attrs['href'])

3 项目实施(深度优先的递归爬虫)

使用深度优先爬虫,爬取所有词条链接,爬虫深度为2,代码如下:

import requests
import re
import time


exist_url = []
news_ids = []
g_writecount = 0


def scrappy(url, depth = 1):
    global g_writecount    
    try:
        headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
        r = requests.get("https://en.wikipedia.org/wiki/" + url, headers= headers)
        html = r.text
    except Exception as e:
        print ('Failed downloading and saving', url)
        print (e)
        exist_url.append(url)
        return None
    
    exist_url.append(url)
    link_list = re.findall('<a href="/wiki/([^:#=<>]*?)".*?</a>',html)
    unique_list = list(set(link_list) - set(exist_url))
    
    for eachone in unique_list:
        g_writecount += 1
        output = "No." + str(g_writecount) + "t Depth:" +  str(depth) + "t"+ url + ' -> ' + eachone + 'n'
        #print (output)
        with open('link_12-3.txt', "a+") as f:
            f.write(output)
            f.close()


        if depth < 2:
            scrappy(eachone, depth+1)


scrappy("Wikipedia")

4 项目进阶(广度优先的多线程爬虫)

import threading  
import requests
import re  
import time  
g_mutex = threading.Condition()  
g_pages = [] #从中解析所有url链接  
g_queueURL = [] #等待爬取的url链接列表  
g_existURL = [] #已经爬取过的url链接列表    
g_writecount = 0 #找到的链接数


class Crawler:  
    def __init__(self,url,threadnum):   
        self.url=url  
        self.threadnum=threadnum  
        self.threadpool=[]   


    def craw(self):  #爬虫的控制大脑,包括爬取网页,更新队列
        global g_queueURL  
        g_queueURL.append(url)
        depth=1
        while(depth < 3):    
            print ('Searching depth ',depth,'...n')  
            self.downloadAll()  
            self.updateQueueURL()
            g_pages = []
            depth += 1  


    def downloadAll(self): #调用多线程爬虫,在小于线程最大值和没爬完队列之前,会增加线程
        global g_queueURL    
        i=0  
        while i<len(g_queueURL):  
            j=0  
            while j<self.threadnum and i+j < len(g_queueURL):    
                threadresult = self.download(g_queueURL[i+j],j)    
                j+=1  
            i += j  
            for thread in self.threadpool:  
                thread.join(30)  
            threadpool=[]  
        g_queueURL=[]  


    def download(self,url,tid): #调用多线程爬虫
        crawthread=CrawlerThread(url,tid)  
        self.threadpool.append(crawthread)  
        crawthread.start()  


    def updateQueueURL(self): #完成一个深度的爬虫之后,更新队列
        global g_queueURL  
        global g_existURL  
        newUrlList=[]  
        for content in g_pages:  
            newUrlList+=self.getUrl(content)  
        g_queueURL=list(set(newUrlList)-set(g_existURL))    


    def getUrl(self,content): #从获取的网页中解析url
        link_list = re.findall('<a href="/wiki/([^:#=<>]*?)".*?</a>',content)
        unique_list = list(set(link_list))
        return unique_list  


class CrawlerThread(threading.Thread): #爬虫线程
    def __init__(self,url,tid):  
        threading.Thread.__init__(self)  
        self.url=url  
        self.tid=tid  
    def run(self):  
        global g_mutex    
        global g_writecount  
        try:
            print (self.tid, "crawl ", self.url)
            headers = {'User-Agent' : 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
            r = requests.get("https://en.wikipedia.org/wiki/" + self.url, headers= headers)
            html = r.text


            link_list2 = re.findall('<a href="/wiki/([^:#=<>]*?)".*?</a>',html)
            unique_list2 = list(set(link_list2))
            for eachone in unique_list2:
                g_writecount += 1
                content2 = "No." + str(g_writecount) + "t Thread" +  str(self.tid) + "t"+ self.url + '->' + eachone +'n'
                with open('title2.txt', "a+") as f:
                    f.write(content2)
                    f.close()
        except Exception as e:
            g_mutex.acquire()  
            g_existURL.append(self.url)   
            g_mutex.release()  
            print ('Failed downloading and saving',self.url)
            print (e)
            return None
        g_mutex.acquire()  
        g_pages.append(html)  
        g_existURL.append(self.url)  
        g_mutex.release()


if __name__ == "__main__":
    url = "Wikipedia"
    threadnum = 5
    crawler = Crawler(url,threadnum)  
    crawler.craw()  

- End -