Python爬虫项目:wiki距离

参考资源:
1.Web Scraping with Python&中文版《Python网络数据采集》
2.廖雪峰hashlib教程

项目内容:
维基百科里面有很多内嵌的链接,我们想看看,从某个名人链接出发,到达另外一个链接的界面,需要经过那些链接。
我们项目是从李嘉诚周杰伦,最后的结果:
['/wiki/Jay_Chou', '/wiki/Liu_Xiang_(hurdler)', '/wiki/Chinese_name', '/wiki/Li_Ka-shing']
总共访问了大概210个网页

项目思路:
这有点像一棵树的遍历,可以层序遍历或者前序遍历,或者说像图的广度遍历和深度遍历,本项目采用广度遍历,因为广度遍历可以计算出最短的计算距离。
代码用一个列表 added_urls存放已经访问过的链接和待访问的链接,这个过程熟悉图的广度遍历的人应该知道,值得注意的是,为了在找到目的链接时可以追溯到底是经过哪些节点找到目标节点的,列表的每个元素不仅仅保存链接还保存父链接在整个列表中的序号
另外为了方便调试,第一次访问某个网站都会在硬盘保存对应的html文件,文件名为网站链接计算出来的哈希值,后面再访问的时候根据链接首先计算出哈希值,再看看有没有对应的文件,如果有,直接读取硬盘上的文件即可,硬盘加载文件可比网络要快得多。

    def get_urls_md5_hash(self,url):
        # 根据urls计算哈希值作为文件名
        md5 = hashlib.md5()
        md5.update(url.encode())
        return md5.hexdigest()

    # 第一次从网络获取并保存到硬盘,第二次从硬盘读取
    def get_urls_html(self,url):
        # 检查链接是否加入了'https://en.wikipedia.org/'
        url = self.checkurl(url)
        # _____________________________________________________________________________________________
        print('正在访问链接{url}...'.format(url = url))

        md5_value = self.get_urls_md5_hash(url)
        filename = '{md5}.html'.format(md5=md5_value)

        if os.path.exists(filename):
            with open(filename,'rb') as f:
                return f.read()
        else:
            # 在网站爬取错误的时候输入错误信息,然后继续
            try:
                html = request.urlopen(url).read()
            except:
                html = None
                print('读取网页失败:{html}'.format(html=html))

            if html:
                with open(filename,'wb') as f:
                    f.write(html)
                return html
            else:
                return html

本项目还采用集合来判断链接之前是不是已经加入added_urls了,集合查找的速度比较快。

    if url not in self.set:
        self.added_urls.append([url,url_index])
        self.set.add(url)

全部代码:

from urllib import request
from bs4 import BeautifulSoup
import hashlib
import re
import os
import time

# 队列用来层序遍历这棵树
# 集合用来检查链接是否已经放入队列过,set查找的速度比较快

LIKASHING = '/wiki/Li_Ka-shing'
JAYCHOU = '/wiki/Jay_Chou'

# 时间测量的装饰器
def timing(f):
    def wrap(*args):
        time1 = time.time()
        ret = f(*args)
        time2 = time.time()
        print('%s function took %0.3f s' % (f.__name__, (time2-time1)))
        return ret
    return wrap

class Spider(object):
    def __init__(self,start_url,end_url):
        self.start_url = start_url
        self.end_url = end_url
        self.added_urls = [[start_url,-1],]
        self.set = set()
        self.set.add(start_url)

    def checkurl(self,url):
        if url.startswith('https'):
            return url
        else:
            return 'https://en.wikipedia.org' + url

    def get_urls_md5_hash(self,url):
        # 根据urls计算哈希值作为文件名
        md5 = hashlib.md5()
        md5.update(url.encode())
        return md5.hexdigest()

    # 第一次从网络获取并保存到硬盘,第二次从硬盘读取
    def get_urls_html(self,url):
        # 检查链接是否加入了'https://en.wikipedia.org/'
        url = self.checkurl(url)
        # _____________________________________________________________________________________________
        print('正在访问链接{url}...'.format(url = url))

        md5_value = self.get_urls_md5_hash(url)
        filename = '{md5}.html'.format(md5=md5_value)

        if os.path.exists(filename):
            with open(filename,'rb') as f:
                return f.read()
        else:
            # 在网站爬取错误的时候输入错误信息,然后继续
            try:
                html = request.urlopen(url).read()
            except:
                html = None
                print('读取网页失败:{html}'.format(html=html))

            if html:
                with open(filename,'wb') as f:
                    f.write(html)
                return html
            else:
                return html

    @timing
    def get_all_wiki_urls(self,url):
        html = self.get_urls_html(url)
        bs_obj = BeautifulSoup(html,'lxml')
        body_content = bs_obj.find('div',id='bodyContent')
        if not body_content:
            print('网页{url}找不到bodyContent'.format(url=url))
            return None

        all_a = body_content.findAll('a',href=re.compile('^(/wiki/)((?!:).)*$'))

        retlist = []
        for a_tag in all_a:
            if 'href' in a_tag.attrs:
                re.match(r'^(/wiki/)',a_tag['href'])
                retlist.append(a_tag['href'])

        return retlist

    # 广度优先遍历wiki
    def find_wiki_degree_bs(self):
        url_index = 0

        while url_index < len(self.added_urls):
            # _____________________________________________________________________________________________
            print('当前的url_index{url_index},元素个数{length}'.format(url_index = url_index,length = len(self.added_urls)))
            url_and_father = self.added_urls[url_index]
            urls = self.get_all_wiki_urls(url_and_father[0])

            if self.end_url in urls:
                print('在{url}找到目标链接了!!!'.format(url = url_and_father[0] ))

                result = [self.end_url,url_and_father[0]]
                temp_father_index = url_and_father[1]

                while temp_father_index >= 0:
                    url_and_father = self.added_urls[temp_father_index]
                    result.append(url_and_father[0])
                    temp_father_index = url_and_father[1]

                return result
            else:
                for url in urls:
                    if url not in self.set:
                        self.added_urls.append([url,url_index])
                        self.set.add(url)

            url_index += 1

        # we can't find until queue is empty
        return None


def main():
    myspider = Spider(LIKASHING,JAYCHOU)
    result = myspider.find_wiki_degree_bs()
    print(result)


if __name__ == '__main__':
    main()

项目后序改进:
1.还可以用深度遍历进行遍历,但是求不出最短的路径,找到有很大的随机性
2.异步实现网络加载,这样子下载网页就会快很多。
3.多进程解析网页和保存网页等等加快程序执行速度。
4.自己实现布隆过滤器来过滤网站减少内存的占用,现在是采用python的集合来存储和判断某个链接是否已经存在,占用内存很大

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容