• 《Python网络爬虫权威指南》读书笔记3(第3章:编写网络爬虫)


    3.1 遍历单个域名

    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    
    html = urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon')
    bs = BeautifulSoup(html, 'html.parser')
    for link in bs.find_all('a'):
        if 'href' in link.attrs:
            print(link.attrs['href'])

    笔者尝试了三次,

    urllib.error.URLError: <urlopen error [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。>

    本书源码处给出了运行结果:https://github.com/REMitchell/python-scraping/blob/master/Chapter03-web-crawlers.ipynb

    如果你仔细观察那些指向词条页面的链接,会发现它们都有3个共同点:

    • 它们都在id是bodyContent的div标签里
    • URL不包含冒号
    • URL都以/wiki/开头

    我们可以利用这些规则稍微调整一下代码来仅获取词条链接:

    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    import re
    
    html = urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon')
    bs = BeautifulSoup(html, 'html.parser')
    
    for link in bs.find('div',{'id':'bodyContent'}).find_all(
        'a',href=re.compile('^(/wiki/)((?!:).)*$')):
        if 'href' in link.attrs:
            print(link.attrs['href'])

    完善程序,使它更像下面的形式:

    • 一个函数getLinks可以用一个/wiki/<词条名称>形式的维基百科词条URL作为参数,然后以同样的形式返回一个列表,里面包含所有的词条URL。
    • 一个主函数,以某个起始词条为参数调用getLinks,然后从返回的URL列表里随机选择一个词条链接,再次调用getLinks,直到你主动停止程序,或者在新的页面上没有词条链接了。
    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    import re
    import datetime
    import random
    
    random.seed(datetime.datetime.now())
    def getLinks(articleUrl):
        html = urlopen('http://en.wikipedia.org{}'.format(articleUrl))
        bs = BeautifulSoup(html, 'html.parser')
        return bs.find('div',{'id':'bodyContent'}).find_all('a',href=re.compile('^(/wiki/)((?!:).)*$'))
    
    links = getLinks('/wiki/Kevin_Bacon')
    while len(links)>0:
        newArticle = links[random.randint(0,len(links)-1)].attrs['href']
        print(newArticle)
        links = getLinks(newArticle)

    由于维基百科打不开,影响了学习效果,笔者决定用百度百度尝试本章要讲的内容。

    首先第一个例子:

    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    html = urlopen('https://baike.baidu.com/item/杨幂')
    bs = BeautifulSoup(html,'html.parser')
    
    for link in bs.find_all('a'):
        if 'href' in link.attrs:
            print(link.attrs['href'])

    发生错误:

    UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-11: ordinal not in range(128)

    通过搜索,只有一种方法有效地解决了我的问题:

    from urllib.request import urlopen,quote
    from bs4 import BeautifulSoup
    
    keyworld = "杨幂"
    key=quote(keyworld)
    url="https://baike.baidu.com/item/s?wd="+key
    html = urlopen(url)
    
    bs = BeautifulSoup(html,'html.parser')
    
    for link in bs.find_all('a'):
        if 'href' in link.attrs:
            print(link.attrs['href'])

    运行结果如下: 

    http://www.baidu.com/
    https://www.baidu.com/
    http://news.baidu.com/
    https://tieba.baidu.com/
    https://zhidao.baidu.com/
    http://music.baidu.com/
    http://image.baidu.com/
    http://v.baidu.com/
    http://map.baidu.com/
    https://wenku.baidu.com/
    /
    /help
    /common/declaration
    /
    /calendar/
    /vbaike/
    /vbaike#gallary
    /art
    /science
    /ziran
    /wenhua
    /dili
    /shenghuo
    /shehui
    /renwu
    /jingji
    /tiyu
    /lishi
    https://child.baidu.com/
    /item/秒懂星课堂
    /item/秒懂大师说
    /item/秒懂看瓦特
    /item/秒懂五千年
    /item/秒懂全视界
    /museum/
    /feiyi?fr=dhlfeiyi
    https://shushuo.baidu.com/
    /city/
    /wikicategory/view?categoryName=恐龙大全
    /wikicategory/view?categoryName=多肉植物
    /kedou/
    /event/ranmeng/
    /task/
    /mall/
    /operation/cooperation#joint
    /operation/cooperation#issue
    /operation/cooperation#connection
    /usercenter
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%A4%9A%E4%B9%89%E8%AF%8D
    /item/%E4%B9%89%E9%A1%B9
    /item/s?force=1
    javascript:;
    /item/S/21248525#viewPageContent
    /item/s/19750332#viewPageContent
    /item/s/19750328#viewPageContent
    /item/s/19830622#viewPageContent
    /item/s/19750329#viewPageContent
    /item/s/20600220#viewPageContent
    /item/s/22401070#viewPageContent
    /divideload/s
    /uc/favolemma
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:;
    javascript:;
    /planet/talk?lemmaId=1084840
    /item/%E6%8B%89%E4%B8%81%E5%AD%97%E6%AF%8D/1936851
    /item/%E7%89%A9%E7%90%86%E5%AD%A6/313183
    /item/%E7%A7%92/2924586
    /item/%E7%A1%AB/721903
    /item/%E5%8C%96%E5%AD%A6%E7%AC%A6%E5%8F%B7/2610154
    /item/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/9845131
    /item/Superman/5161
    /item/%E5%BA%8F%E6%95%B0
    /item/%E5%8E%9F%E5%AD%90
    /item/VIA
    #1
    #2
    #3
    #3_1
    #3_2
    #3_3
    #3_4
    #3_5
    #3_6
    #3_7
    #3_8
    #4
    javascript:;
    /item/%E9%9D%9E%E9%87%91%E5%B1%9E
    /item/%E5%8E%9F%E5%AD%90%E5%BA%8F%E6%95%B0
    /item/%E6%B0%A7%E6%97%8F%E5%85%83%E7%B4%A0
    /item/%E5%85%83%E7%B4%A0%E5%91%A8%E6%9C%9F%E8%A1%A8/282048
    /item/%E5%91%A8%E6%9C%9F/2174752
    javascript:;
    /item/%E8%8B%B1%E6%96%87%E5%AD%97%E6%AF%8D
    /item/19/6373
    /item/%E5%90%AB%E4%B9%89
    /pic/s/1084840/0/f3d3572c11dfa9ec6365239b62d0f703908fc1f7?fr=lemma&ct=single
    /item/%E9%9F%B3%E6%A0%87
    /item/Sierra
    /item/%E5%AD%97%E6%AF%8D
    /item/%E5%8F%A4%E8%AF%AD
    /item/long/70947
    /item/%E7%BB%93%E5%B0%BE/1465854
    /item/%E5%BE%B7%E8%AF%AD
    /item/%E5%A4%A7%E5%86%99
    /item/%E5%B0%8F%E5%86%99
    /item/Mrs/7290452
    /item/Miss/8425466
    /item/%E7%94%B7%E5%A3%AB/10001620
    /item/R
    /item/%E5%B0%8F%E5%8F%B7/39895
    /item/%E8%B6%85%E4%BA%BA/13106
    /item/superman
    javascript:;
    /item/%E5%9B%BE%E5%BD%A2%E9%9D%A2%E7%A7%AF
    /item/%E7%BB%9F%E8%AE%A1/19954318
    /item/%E6%A0%87%E5%87%86%E5%B7%AE
    /item/%E7%89%A9%E7%90%86%E5%AD%A6/313183
    /item/%E7%A7%92/2924586
    /item/%E5%8D%95%E4%BD%8D/32292
    /item/%E7%94%B5%E7%BA%B3
    /item/%E7%94%B5%E8%B7%AF/33197
    /item/%E5%A4%8D%E6%95%B0/254365
    /item/%E7%94%B5%E5%AF%BC/1016733
    /item/%E8%A5%BF%E9%97%A8%E5%AD%90/2407554
    /item/%E5%9B%BD%E9%99%85%E5%8D%95%E4%BD%8D%E5%88%B6
    /item/%E7%94%B5%E7%BA%B3
    /item/%E6%BA%B6%E8%A7%A3%E5%BA%A6
    /item/%E6%96%B9%E7%A8%8B%E5%BC%8F
    /item/%E6%B2%89%E9%99%8D%E7%B3%BB%E6%95%B0
    /item/%E5%88%86%E5%AD%90/479055
    /item/%E8%85%B0%E5%9B%B4
    /item/%E8%87%80%E5%9B%B4
    /item/%E8%82%A9%E5%AE%BD
    /item/%E5%90%88%E5%94%B1/3002
    /item/%E5%A3%B0%E9%83%A8/5079754
    /item/%E8%87%AA%E5%8A%A8%E6%9B%9D%E5%85%89/2577367
    /item/%E5%BF%AB%E9%97%A8/82245
    /item/%E8%87%AA%E5%8A%A8/9374325
    javascript:;
    /item/%E7%AE%80%E7%A7%B0/10492947
    /pic/s/1084840/0/f636afc379310a5519aea991b94543a9832610c8?fr=lemma&ct=single
    /vbaike#gallary
    /historylist/s/1084840
    /usercenter/userpage?uname=%E9%9B%B6%E5%BA%A6_%E5%8F%AF%E4%B9%90&from=lemma
    /usercenter/userpage?uname=%E6%B1%9F%E5%AE%89%E6%89%8D%E5%AD%90&from=lemma
    https://baike.baidu.com/item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%88%9B%E5%BB%BA%E7%89%88%E6%9C%AC
    #1
    #2
    #3
    #3_1
    #3_2
    #3_3
    #3_4
    #3_5
    #3_6
    #3_7
    #3_8
    #4
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    /usercenter/tasks#guide
    /help#main01
    /help#main06
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E6%9C%AC%E4%BA%BA%E8%AF%8D%E6%9D%A1%E7%BC%96%E8%BE%91%E6%9C%8D%E5%8A%A1/22442459?bk_fr=pcFooter
    javascript:void(0);
    http://zhiqiu.baidu.com/baike/passport/html/baikechat.html
    http://tieba.baidu.com/f?ie=utf-8&fr=bks0000&kw=%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91
    javascript:void(0);
    http://help.baidu.com/newadd?prod_id=10&category=1
    http://help.baidu.com/newadd?prod_id=10&category=2
    http://help.baidu.com/newadd?prod_id=10&category=6
    http://help.baidu.com/newadd?prod_id=10&category=5
    http://www.baidu.com/duty/
    http://help.baidu.com/question?prod_en=baike&class=89&id=1637
    http://help.baidu.com/question?prod_id=10&class=690&id=1001779
    /operation/cooperation
    http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002000001
    /
    javascript:;
    /planet/talk?lemmaId=1084840
    javascript:;
    javascript:;
    javascript:;
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);
    javascript:void(0);

    如果你仔细观察那些指向词条页面(不是指向其他内容页面)的链接会发现它们都有两个共同点:

    • URL 链接不包含分号
    • URL 链接都以 /item/ 开头

    修改:

    from urllib.request import urlopen,quote
    from bs4 import BeautifulSoup
    import re
    
    keyworld = "杨幂"
    key=quote(keyworld)
    url="https://baike.baidu.com/item/s?wd="+key
    html = urlopen(url)
    
    bs = BeautifulSoup(html,'html.parser')
    
    for link in bs.find('html').find_all(
        'a',href=re.compile('^(/item/)((?!:).)*$')):
        if 'href' in link.attrs:
            print(link.attrs['href'])

    输出:

    /item/秒懂星课堂
    /item/秒懂大师说
    /item/秒懂看瓦特
    /item/秒懂五千年
    /item/秒懂全视界
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%A4%9A%E4%B9%89%E8%AF%8D
    /item/%E4%B9%89%E9%A1%B9
    /item/s?force=1
    /item/S/21248525#viewPageContent
    /item/s/19750332#viewPageContent
    /item/s/19750328#viewPageContent
    /item/s/19830622#viewPageContent
    /item/s/19750329#viewPageContent
    /item/s/20600220#viewPageContent
    /item/s/22401070#viewPageContent
    /item/%E6%8B%89%E4%B8%81%E5%AD%97%E6%AF%8D/1936851
    /item/%E7%89%A9%E7%90%86%E5%AD%A6/313183
    /item/%E7%A7%92/2924586
    /item/%E7%A1%AB/721903
    /item/%E5%8C%96%E5%AD%A6%E7%AC%A6%E5%8F%B7/2610154
    /item/%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/9845131
    /item/Superman/5161
    /item/%E5%BA%8F%E6%95%B0
    /item/%E5%8E%9F%E5%AD%90
    /item/VIA
    /item/%E9%9D%9E%E9%87%91%E5%B1%9E
    /item/%E5%8E%9F%E5%AD%90%E5%BA%8F%E6%95%B0
    /item/%E6%B0%A7%E6%97%8F%E5%85%83%E7%B4%A0
    /item/%E5%85%83%E7%B4%A0%E5%91%A8%E6%9C%9F%E8%A1%A8/282048
    /item/%E5%91%A8%E6%9C%9F/2174752
    /item/%E8%8B%B1%E6%96%87%E5%AD%97%E6%AF%8D
    /item/19/6373
    /item/%E5%90%AB%E4%B9%89
    /item/%E9%9F%B3%E6%A0%87
    /item/Sierra
    /item/%E5%AD%97%E6%AF%8D
    /item/%E5%8F%A4%E8%AF%AD
    /item/long/70947
    /item/%E7%BB%93%E5%B0%BE/1465854
    /item/%E5%BE%B7%E8%AF%AD
    /item/%E5%A4%A7%E5%86%99
    /item/%E5%B0%8F%E5%86%99
    /item/Mrs/7290452
    /item/Miss/8425466
    /item/%E7%94%B7%E5%A3%AB/10001620
    /item/R
    /item/%E5%B0%8F%E5%8F%B7/39895
    /item/%E8%B6%85%E4%BA%BA/13106
    /item/superman
    /item/%E5%9B%BE%E5%BD%A2%E9%9D%A2%E7%A7%AF
    /item/%E7%BB%9F%E8%AE%A1/19954318
    /item/%E6%A0%87%E5%87%86%E5%B7%AE
    /item/%E7%89%A9%E7%90%86%E5%AD%A6/313183
    /item/%E7%A7%92/2924586
    /item/%E5%8D%95%E4%BD%8D/32292
    /item/%E7%94%B5%E7%BA%B3
    /item/%E7%94%B5%E8%B7%AF/33197
    /item/%E5%A4%8D%E6%95%B0/254365
    /item/%E7%94%B5%E5%AF%BC/1016733
    /item/%E8%A5%BF%E9%97%A8%E5%AD%90/2407554
    /item/%E5%9B%BD%E9%99%85%E5%8D%95%E4%BD%8D%E5%88%B6
    /item/%E7%94%B5%E7%BA%B3
    /item/%E6%BA%B6%E8%A7%A3%E5%BA%A6
    /item/%E6%96%B9%E7%A8%8B%E5%BC%8F
    /item/%E6%B2%89%E9%99%8D%E7%B3%BB%E6%95%B0
    /item/%E5%88%86%E5%AD%90/479055
    /item/%E8%85%B0%E5%9B%B4
    /item/%E8%87%80%E5%9B%B4
    /item/%E8%82%A9%E5%AE%BD
    /item/%E5%90%88%E5%94%B1/3002
    /item/%E5%A3%B0%E9%83%A8/5079754
    /item/%E8%87%AA%E5%8A%A8%E6%9B%9D%E5%85%89/2577367
    /item/%E5%BF%AB%E9%97%A8/82245
    /item/%E8%87%AA%E5%8A%A8/9374325
    /item/%E7%AE%80%E7%A7%B0/10492947
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E6%9C%AC%E4%BA%BA%E8%AF%8D%E6%9D%A1%E7%BC%96%E8%BE%91%E6%9C%8D%E5%8A%A1/22442459?bk_fr=pcFooter

     完善程序,使它更像下面的形式:

    • 一个函数getLinks可以用一个/wiki/<词条名称>形式的维基百科词条URL作为参数,然后以同样的形式返回一个列表,里面包含所有的词条URL。
    • 一个主函数,以某个起始词条为参数调用getLinks,然后从返回的URL列表里随机选择一个词条链接,再次调用getLinks,直到你主动停止程序,或者在新的页面上没有词条链接了。
    from urllib.request import urlopen,quote
    from bs4 import BeautifulSoup
    import re
    
    import datetime
    import random
    
    random.seed(datetime.datetime.now())
    def getLinks(articleUrl):
        html = urlopen('https://baike.baidu.com'.format(articleUrl))
        bs = BeautifulSoup(html, 'html.parser')
        return bs.find('html').find_all('a',href=re.compile('^(/item/)((?!:).)*$'))
    
    links = getLinks('/item/%E6%9D%A8%E5%B9%82')
    while len(links)>0:
        newArticle = links[random.randint(0,len(links)-1)].attrs['href']
        print(newArticle)
        links = getLinks(newArticle)

    3.2 抓取整个网站

    全面彻底地抓取网站的常用方法是从一个顶级页面(比如主页)开始 ,然后搜索该页面上的所有内链,形成列表。之后,抓取这些链接跳转到的每一个页面,再把再每个页面上找到的链接形成新的列表,接着执行下一轮抓取。

    为了避免一个页面被抓取两次,链接去重是非常重要的。在代码运行时,要把已发现的所有链接都放到一起,并保存在方便查询的集合(set)里。集合中的元素没有特定的顺序,集合只存储唯一的元素。

    from urllib.request import urlopen,quote
    from bs4 import BeautifulSoup
    import re
    
    pages = set()
    def getLinks(pageUrl):
        global pages
        html = urlopen('https://baike.baidu.com'.format(pageUrl))
        bs = BeautifulSoup(html,'html.parser')
        for link in bs.find_all('a',href=re.compile('^(/item/)')):
            if 'href' in link.attrs:
                if link.attrs['href'] not in pages:
                    # we have encountered a new page
                    newPage = link.attrs['href']
                    print(newPage)
                    pages.add(newPage)
                    getLinks(newPage)
    getLinks('')

    一开始,用getLinks处理一个空URL,其实就是百度百科的主页,因为在函数里空URL就是https://baike.baidu.com。然后,遍历首页上的每个链接,并检查它是否已经在全局变量集合pages里面了。如果不在,就添加到集合中,并打印到屏幕上,再用getLinks递归处理这个链接。

    关于递归的警告

    这个警告在软件开发类图书里很少提到:如果递归运行的次数非常多,前面的递归程序很可能会崩溃。

    Python默认的递归限制是1000次。

    收集整个网站的数据

    观察网站的页面,然后拟定抓取模式。

    • 词条标题都继承自h1标签里。
    • 第一段文字继承自'div',{'class':'para'}或'meta',{'class':'description'里。
    • 编辑链接继承自'a',{'class':'edit-icon j-edit-link'}或'a',{'class':'edit-lemma cmn-btn-hover-blue cmn-btn-28 j-edit-link'}

    调整代码:

    from urllib.request import urlopen,quote
    from bs4 import BeautifulSoup
    import re
    
    pages = set()
    def getLinks(pageUrl):
        global pages
        try:
            html = urlopen('https://baike.baidu.com{}'.format(pageUrl))
        except UnicodeEncodeError:
            urllist = pageUrl.split('/')
            keyword = urllist[2]
            key = quote(keyword)
            url = "https://baike.baidu.com/item/"+key
            html = urlopen(url)
        bs = BeautifulSoup(html,'html.parser')
        try:
            print(bs.h1.get_text())
            print(bs.find('div',{'class':'para'}) or bs.find('meta',{'class':'description'}))
            print(bs.find('a',{'class':'edit-icon j-edit-link'}).attrs['href'] or
                  bs.find('a',{'class':'edit-lemma cmn-btn-hover-blue cmn-btn-28 j-edit-link'}).attrs['href'])
        except AttributeError:
            print("页面缺少一些属性!")
        for link in bs.find_all('a',href=re.compile('^(/item/)')):
            if 'href' in link.attrs:
                if link.attrs['href'] not in pages:
                    # we have encountered a new page
                    newPage = link.attrs['href']
                    print('-'*20)
                    print(newPage)
                    pages.add(newPage)
                    getLinks(newPage)
    getLinks('/item/%E6%9D%A8%E5%B9%82')

    输出结果片段:

    杨幂
    <div class="para" label-module="para">杨幂,1986年9月12日出生于北京市,中国内地影视女演员、流行乐歌手、影视制片人。</div>
    javascript:;
    --------------------
    /item/秒懂星课堂
    秒懂星课堂
    <div class="para" label-module="para">“秒懂星课堂”是<a data-lemmaid="85895" href="/item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91/85895" target="_blank">百度百科</a>2017年推出的明星<a data-lemmaid="20596678" href="/item/%E7%9F%AD%E8%A7%86%E9%A2%91/20596678" target="_blank">短视频</a>栏目。邀请时下热门明星录制3分钟左右短视频,向大众分享一个知识点,让复杂的知识更简单的同时,带你了解明星的另一面。</div>
    javascript:;
    --------------------
    /item/秒懂大师说
    秒懂大师说
    <div class="para" label-module="para">“秒懂大师说”是百度百科2017全新推出的秒懂百科子栏目,邀请中外各领域顶尖学者、智囊、权威人士分享解读当今社会最引人关注的热点趋势与话题,传授各领域最独到、权威的观点知识。</div>
    javascript:;
    --------------------
    /item/秒懂看瓦特
    秒懂看瓦特
    <div class="para" label-module="para">《秒懂看瓦特》是2018年百度百科推出的影视综解说的短视频栏目。《秒懂看瓦特》联合多位优秀的影评人,解说当下最热的电影、电视剧、综艺等娱乐内容,帮助更多人快速认识到“看what”?!</div>
    javascript:;
    --------------------
    /item/秒懂五千年
    秒懂五千年
    <div class="para" label-module="para">“秒懂五千年”是百度百科2018年全新推出的科普中国传统文化原创动画栏目。该栏目构思新颖风趣幽默,遵循中国历史的发展脉络引出了一个个生动有趣的传统文化故事,为青少年再现了中华民族五千年璀璨的文明。</div>
    javascript:;
    --------------------
    /item/秒懂全视界
    秒懂全视界
    <div class="para" label-module="para">『秒懂全视界』是百度百科与互动视界2017年12月联合推出的VR旅游短视频栏目。</div>
    页面缺少一些属性!
    --------------------
    /item/%E5%B7%B4%E5%A1%9E%E7%BD%97%E9%82%A3/36870
    巴塞罗那
    <div class="para" label-module="para">巴塞罗那(Barcelona)位于<a data-lemmaid="973488" href="/item/%E4%BC%8A%E6%AF%94%E5%88%A9%E4%BA%9A%E5%8D%8A%E5%B2%9B/973488" target="_blank">伊比利亚半岛</a>东北部,濒临<a data-lemmaid="11515" href="/item/%E5%9C%B0%E4%B8%AD%E6%B5%B7/11515" target="_blank">地中海</a>,是<a data-lemmaid="148941" href="/item/%E8%A5%BF%E7%8F%AD%E7%89%99/148941" target="_blank">西班牙</a>第二大城市,也是<a href="/item/%E5%8A%A0%E6%B3%B0%E7%BD%97%E5%B0%BC%E4%BA%9A" target="_blank">加泰罗尼亚</a>自治区首府,以及<a data-lemmaid="6420791" href="/item/%E5%B7%B4%E5%A1%9E%E7%BD%97%E9%82%A3%E7%9C%81/6420791" target="_blank">巴塞罗那省</a>(隶属于加泰罗尼亚自治区)的省会,加泰罗尼亚自治区议会、<a data-lemmaid="11036959" href="/item/%E8%A1%8C%E6%94%BF%E6%9C%BA%E6%9E%84/11036959" target="_blank">行政机构</a>、高等法院均设立于此。</div>
    javascript:;
    --------------------
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91%EF%BC%9A%E5%A4%9A%E4%B9%89%E8%AF%8D
    百度百科:多义词
    <div class="para" label-module="para"><a data-lemmaid="85895" href="/item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91/85895" target="_blank">百度百科</a>里,当同一个词条名可指代含义概念不同的事物时,这个词条称为多义词。如词条“苹果”,既可以代表一种水果,也可以指代苹果公司,因此“苹果”是一个多义词。</div>
    页面缺少一些属性!
    --------------------
    /item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91/85895
    百度百科
    <div class="para" label-module="para">百度百科是<a data-lemmaid="8567456" href="/item/%E7%99%BE%E5%BA%A6%E5%85%AC%E5%8F%B8/8567456" target="_blank">百度公司</a>推出的一部内容开放、自由的网络百科全书。其测试版于2006年4月20日上线,正式版在2008年4月21日发布,截至2019年5月,百度百科已经收录了超1370万词条,参与词条编辑的网友超过680万人,几乎涵盖了所有已知的知识领域。<sup class="sup--normal" data-ctrmap=":1," data-sup="1">
    [1]</sup><a class="sup-anchor" name="ref_[1]_1"> </a>
    </div>
    javascript:;
    --------------------
    /item/%E7%99%BE%E5%BA%A6%E5%85%AC%E5%8F%B8/8567456
    百度
    <div class="para" label-module="para">百度<i>(纳斯达克:BIDU)</i>,全球最大的中文搜索引擎及最大的中文网站,全球领先的人工智能公司。百度愿景是:成为最懂用户,并能帮助人们成长的全球顶级高科技公司。<sup class="sup--normal" data-ctrmap=":1," data-sup="1">
    [1]</sup><a class="sup-anchor" name="ref_[1]_5583090"> </a>
    <b><b></b></b></div>
    页面缺少一些属性!
    --------------------
    /item/%E4%B9%89%E9%A1%B9
    义项
    <div class="para" label-module="para"><a href="/item/%E7%99%BE%E7%A7%91%E5%A4%9A%E4%B9%89%E8%AF%8D" target="_blank">百科多义词</a>中,每一个不同概念意义事物的叙述内容称为义项。</div>
    页面缺少一些属性!
    --------------------
    /item/%E7%99%BE%E7%A7%91%E5%A4%9A%E4%B9%89%E8%AF%8D
    百度百科:多义词
    <div class="para" label-module="para"><a data-lemmaid="85895" href="/item/%E7%99%BE%E5%BA%A6%E7%99%BE%E7%A7%91/85895" target="_blank">百度百科</a>里,当同一个词条名可指代含义概念不同的事物时,这个词条称为多义词。如词条“苹果”,既可以代表一种水果,也可以指代苹果公司,因此“苹果”是一个多义词。</div>
    页面缺少一些属性!
    --------------------
    /item/%E8%B5%B5%E6%B0%8F%E5%AD%A4%E5%84%BF
    赵氏孤儿大报仇
    <div class="para" label-module="para">《赵氏孤儿大报仇》(又名《冤报冤赵氏孤儿》、《赵氏孤儿冤报冤》,简称《赵氏孤儿》)是元代<a data-lemmaid="1456860" href="/item/%E7%BA%AA%E5%90%9B%E7%A5%A5/1456860" target="_blank">纪君祥</a>创作的杂剧,全剧五折一楔子。</div>
    javascript:;
    --------------------

    处理重定向

    重定向使得Web服务器可以将一个域名或者URL指向不同位置的内容。重定向有两种类型:

    • 服务器端重定向,在页面加载前URL就会发生改变;
    • 客户端重定向,又是我们可以看到“页面将在10秒钟内跳转”这类消息,这里 页面跳转到新页面之前已经加载。

    如果使用的python3.x的urlib库,它可以自动处理重定向的问题。

    3.3 在互联网上抓取

    之后,我们通过parsed的各个属性来访问不同的部分
    
    from urlparse import urlparse
    
    parsed = urlparse('url地址')
    
    print 'scheme  :'+ parsed.scheme   #网络协议
    
    print 'netloc  :'+ parsed.netloc   #服务器位置(也可呢能有用户信息)
    
    print 'path    :'+ parsed.path     #网页文件在服务器中存放的位置
    
    print 'params  :'+ parsed.params   #可选参数
    
    print 'query   :'+ parsed.query    #连接符(&)连接键值对
    
    print 'fragment:'+ parsed.fragment #拆分文档中的特殊猫
    
    print 'username:'+ parsed.username #用户名
    
    print 'password:'+ parsed.password #密码
    
    print 'hostname:'+ parsed.hostname #服务器名称或者地址
    
    print 'port    :', parsed.port     #端口(默认是80

    将几个python函数组合起来就可以实现不同类型的网络爬虫:

    from urllib.request import urlopen
    from urllib.parse import urlparse
    from bs4 import BeautifulSoup
    import re
    import datetime
    import random
    
    pages = set()
    random.seed(datetime.datetime.now())
    
    # 获取页面中所有内链的列表
    def getInternalLinks(bs,includeUrl):
        includeUrl = '{}://{}'.format(urlparse(includeUrl).scheme,
                                      urlparse(includeUrl).netloc)
        internalLinks = []
        # 找出所有以“/”开头的链接
        for link in bs.find_all('a',
            href=re.compile('^(/|.*'+includeUrl+')')):
            if link.attrs['href'] is not None:
                if link.attrs['href'] not in internalLinks:
                    if(link.attrs['href'].startswith('/')):
                        internalLinks.append(
                            includeUrl+link.attrs['href'])
                    else:
                        internalLinks.append(link.attrs['href'])
        return internalLinks
    # 获取页面中所有的外链的列表
    def getExternalLinks(bs,excludeUrl):
        externalLinks = []
        # 找出所有以"http"或"www"开头且不包含当前URL的链接
        for link in bs.find_all('a',
            href=re.compile('^(http|www)((?!'+excludeUrl+').)*$')):
            if link.attrs['href'] is not None:
                if link.attrs['href'] not in externalLinks:
                    externalLinks.append(link.attrs['href'])
        return externalLinks
    def getRandomExternalLink(startingPage):
        html = urlopen(startingPage)
        bs = BeautifulSoup(html,'html.parser')
        externalLinks = getExternalLinks(bs,
            urlparse(startingPage).netloc)
        if len(externalLinks) == 0:
            print('No external links,looking around the site for one')
            domain = '{}://{}'.format(urlparse(startingPage).scheme,
                                      urlparse(startingPage).netloc)
            internalLinks = getInternalLinks(bs,domain)
            return getRandomExternalLink(internalLinks[random.randint(0,
                                                        len(internalLinks)-1)])
        else:
            return externalLinks[random.randint(0,len(externalLinks)-1)]
    
    def followExternalOnly(startingSite):
        externalLink = getRandomExternalLink(startingSite)
        print('Random external link is:{}'.format(externalLink))
        followExternalOnly(externalLink)
    followExternalOnly('https://baidu.com/')

    得到结果:

    Random external link is:http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002000001
    Random external link is:http://cyberpolice.mps.gov.cn/wfjb
    Random external link is:http://net.china.com.cn/
    Random external link is:http://www.12377.cn/txt/2019-08/14/content_40863328.htm
    No external links,looking around the site for one

    期间可能会发生一些错误:

    2.HTTPError
    服务器上每一个HTTP 应答对象response包含一个数字"状态码"。
    
    有时状态码指出服务器无法完成请求。默认的处理器会为你处理一部分这种应答。
    
    例如:假如response是一个"重定向",需要客户端从别的地址获取文档,urllib2将为你处理。
    
    其他不能处理的,urlopen会产生一个HTTPError。
    
    典型的错误包含"404"(页面无法找到),"403"(请求禁止),和"401"(带验证请求)。
    
    HTTP状态码表示HTTP协议所返回的响应的状态。
    
    比如客户端向服务器发送请求,如果成功地获得请求的资源,则返回的状态码为200,表示响应成功。
    
    如果请求的资源不存在, 则通常返回404错误。 
    
    HTTP状态码通常分为5种类型,分别以1~5五个数字开头,由3位整数组成:
    
    ------------------------------------------------------------------------------------------------
    
    200:请求成功      处理方式:获得响应的内容,进行处理 
    
    201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到    处理方式:爬虫中不会遇到 
    
    202:请求被接受,但处理尚未完成    处理方式:阻塞等待 
    
    204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。    处理方式:丢弃
    
    300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。    处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃
    301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源    处理方式:重定向到分配的URL
    302:请求到的资源在一个不同的URL处临时保存     处理方式:重定向到临时的URL 
    
    304 请求的资源未更新     处理方式:丢弃 
    
    400 非法请求     处理方式:丢弃 
    
    401 未授权     处理方式:丢弃 
    
    403 禁止     处理方式:丢弃 
    
    404 没有找到     处理方式:丢弃 
    
    5XX 回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求    处理方式:丢弃
     ———————————————— 
    版权声明:本文为CSDN博主「请叫我汪海」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/pleasecallmewhy/article/details/8923725

    所以,需要将其和第1章中介绍的处理网络链接异常的代码结合起来。这样当出现HTTP错误或者服务器异常时,代码就可以选择一个不同的URL。

    增加一个函数,收集在网站上发现的所有外链列表

    # 收集在网站上发现的所有外链列表
    allExtLinks = set()
    allIntLinks = set()
    def getAllExternalLinks(siteUrl):
        try:
            html = urlopen(siteUrl)
            domain = '{}://{}'.format(urlparse(siteUrl).scheme,
            urlparse(siteUrl).netloc)
        except HTTPError as e:
            return None
        except URLError as e:
            return None
        else:
            bs = BeautifulSoup(html,'html.parser')
            internalLinks = getInternalLinks(bs,domain)
            externalLinks = getExternalLinks(bs,domain)
            for link in externalLinks:
                if link not in allExtLinks:
                    allExtLinks.add(link)
                    print(link)
            for link in internalLinks:
                if link not in allIntLinks:
                    allIntLinks.add(link)
                    getAllExternalLinks(link)
    allIntLinks.add('http://oreilly.com')
    getAllExternalLinks('http://oreilly.com')

    增加了异常处理,得到下面的结果:

    https://www.oreilly.com
    https://www.oreilly.com/sign-in.html
    https://www.oreilly.com/online-learning/try-now.html
    https://www.oreilly.com/online-learning/index.html
    --ship--
    https://www.safaribooksonline.com/static/legal/SafariPrivacyPolicy_v3.3_13June2017.a4d9478408f5.pdf
    https://www.oreilly.com/terms/guidelines.html
    https://learning.oreilly.com/membership-agreement/
    https://creativecommons.org/licenses/by-sa/3.0/
    https://www.copyright.gov/title17/92chap1.html#107
    http://radar.oreilly.com/2009/01/work-on-stuff-that-matters-fir.html
  • 相关阅读:
    Java开发笔记(四十二)日历工具的常见应用
    Java开发笔记(四十一)日历工具Calendar
    Java开发笔记(四十)日期与字符串的互相转换
    Java开发笔记(三十九)日期工具Date
    Java开发笔记(三十八)利用正则表达式校验字符串
    Java开发笔记(三十七)利用正则串分割字符串
    Java开发笔记(三十六)字符串的常用方法
    Java开发笔记(三十五)字符串格式化
    Git 工作流程
    如果你恨一个程序员,忽悠他去做iOS开发
  • 原文地址:https://www.cnblogs.com/cathycheng/p/11378193.html
Copyright © 2020-2023  润新知