《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

 

posted @ 2019-08-23 10:24  橘子酱ing  阅读(2235)  评论(0编辑  收藏  举报