《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