python爬虫之遍历单个域名
即使你没听说过“维基百科六度分隔理论”,也很可能听过“凯文•贝肯(KevinBacon)的六度分隔值游戏”。在这两个游戏中,目标都是把两个不相干的主题(在前一种情况中是相互链接的维基百科词条,而在后一种情况中是出现在同一部电影中的演员)用一个链条(至多包含6个主题,包括原来的两个主题)连接起来。
比如,埃里克•艾德尔和布兰登•弗雷泽都出现在电影《骑警杜德雷》里,布兰登•弗雷泽又和凯文•贝肯都出现在电影《我呼吸的空气》里。因此,根据这两个条件,从埃里克•艾德尔到凯文•贝肯的链条长度只有3个主题。
感谢TheOracleofBacon的存在,满足了我对这类关系链的好奇心。
我们将在本节创建一个项目来实现“维基百科六度分隔理论”的查找方法。也就是说,我们要实现从埃里克•艾德尔的词条页面(https://en.wikipedia.org/wiki/Eric_Idle)开始,经过最少的链接点击次数找到凯文•贝肯的词条页面(https://en.wikipedia.org/wiki/Kevin_Bacon)。
这么做对维基百科的服务器负载有多大影响?
根据维基媒体基金会(维基百科所属的组织)的统计,该网站每秒会收到大约2500次点击,其中超过99%的点击都指向维基百科域名[详情请见“维基媒体统计图”(WikimediainFigures)里的“流量数据”(TrafficVolume)部分内容]。因为网站流量很大,所以你的网络爬虫不可能对维基百科的服务器负载产生显著影响。不过,如果你频繁地运行本书的代码示例,或者自己创建项目来抓取维基百科的词条,那么希望你能够向维基媒体基金会提供一点捐赠——不只是为了抵消你占用的服务器资源,也是为了其他人能够利用维基百科这个教育资源。
还需要注意的是,如果你准备利用维基百科的数据做一个大型项目,应该确认该数据是不能够通过维基百科API获取的。维基百科网站经常被用于演示爬虫,因为它的HTML结构简单并且相对稳定。但是它的API往往会使得数据获取更加高效。你应该已经知道如何写一段Python代码,来获取维基百科网站的任何页面并提取该页面中的链接了。
fromurllib.requestimporturlopenfrombs4importBeautifulSoup html=urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon') bs=BeautifulSoup(html,'html.parser') forlinkinbs.find_all('a'): if'href'inlink.attrs: print(link.attrs['href'])
如果你观察生成的一列链接,会看到你想要的所有词条链接都在里面:“Apollo13”“Philadelphia”“PrimetimeEmmyAward”,等等。但是,也有一些你不需要的链接:
//wikimediafoundation.org/wiki/Privacy_policy
//en.wikipedia.org/wiki/Wikipedia:Contact_us
其实,维基百科的每个页面都充满了侧边栏、页眉和页脚链接,以及连接到分类页面、对话页面和其他不包含词条的页面的链接:
/wiki/Category:Articles_with_unsourced_statements_from_April_2014 /wiki/Talk:Kevin_Bacon
最近我有个朋友在做一个类似的维基百科抓取项目,他说,为了判断一个维基百科内链是否链接到一个词条页面,他写了一个很大的过滤函数,代码超过了100行。不幸的是,他没有提前花很多时间去寻找“词条链接”和“其他链接”之间的模式,也可能他后来发现了。如果你仔细观察那些指向词条页面(不是指向其他内部页面)的链接,会发现它们都有3个共同点:
- 它们都在id是bodyContent的div标签里
- URL不包含冒号
- URL都以/wiki/开头
我们可以利用这些规则稍微调整一下代码来仅获取词条链接,使用的正则表达式为^(/wiki/)((?!:).)*$"):
fromurllib.requestimporturlopen frombs4importBeautifulSoup importre html=urlopen('http://en.wikipedia.org/wiki/Kevin_Bacon') bs=BeautifulSoup(html,'html.parser') forlinkinbs.find('div',{'id':'bodyContent'}).find_all( 'a',href=re.compile('^(/wiki/)((?!:).)*$')): if'href'inlink.attrs: print(link.attrs['href'])
如果你运行以上代码,就会看到维基百科上凯文•贝肯词条里所有指向其他词条的链接。
当然,写程序来找出这个静态的维基百科词条里所有的词条链接很有趣,不过没什么实际用处。你需要让这段程序更像下面的形式。
- 一个函数getLinks,可以用一个/wiki/<词条名称>形式的维基百科词条URL作为参数,然后以同样的形式返回一个列表,里面包含所有的词条URL。
- 一个主函数,以某个起始词条为参数调用getLinks,然后从返回的URL列表里随机选择一个词条链接,再次调用getLinks,直到你主动停止程序,或者在新的页面上没有词条链接了。
完整的代码如下所示:
fromurllib.requestimporturlopen frombs4importBeautifulSoup importdatetime importrandom importre random.seed(datetime.datetime.now()) defgetLinks(articleUrl):html=urlopen('http://en.wikipedia.org{}'.format(articleUrl)) bs=BeautifulSoup(html,'html.parser') returnbs.find('div',{'id':'bodyContent'}).find_all('a', href=re.compile('^(/wiki/)((?!:).)*$')) links=getLinks('/wiki/Kevin_Bacon') whilelen(links)>0: newArticle=links[random.randint(0,len(links)-1)].attrs['href'] print(newArticle) links=getLinks(newArticle)
导入需要的Python库之后,程序首先做的是用系统当前时间设置随机数生成器的种子。这样可以保证每次程序运行的时候,维基百科词条的选择都是一个全新的随机路径。
伪随机数和随机数种子
在前面的示例中,为了能够连续地随机遍历维基百科,我用Python的随机数生成器在每个页面上随机选择一个词条链接。但是,用随机数的时候需要格外小心。
虽然计算机很擅长做精确计算,但是它们处理随机事件时非常不靠谱。因此,随机数是一个难题。大多数随机数算法都努力生成一个呈均匀分布且难以预测的数字序列,但是在算法初始化阶段都需要提供一个随机数“种子”(randomseed)。而完全相同的种子每次将生成同样的“随机”数序列,因此我将系统时间作为生成新随机数序列(和新随机词条序列)的起点。这样做会让程序运行的时候更具有随机性。
其实,Python的伪随机数生成器用的是梅森旋转(MersenneTwister)算法,它生成的随机数很难预测且呈均匀分布,就是有点儿耗费CPU资源。真正好的随机数可不便宜!然后,程序定义getLinks函数,它接收一个/wiki/<词条名称>形式的维基百科词条URL作为参数,在前面加上维基百科的域名http://en.wikipedia.org,再用该域名的HTML获得一个BeautifulSoup对象。之后,基于前面介绍过的参数,抽取一列词条链接所在的标签a并返回它们。程序的主函数首先把起始页面https://en.wikipedia.org/wiki/Kevin_Bacon里的词条链接列表设置成链接标签列表(links变量)。然后用一个循环,从页面中随机找一个词条链接标签并抽取href属性,打印这个页面,再把这个链接传入getLinks函数,重新获取新的链接列表。
当然,这里只是简单地构建一个从一个页面到另一个页面的爬虫,要解决“维基百科六度分隔理论”问题还需要再做一点儿工作。我们还应该存储URL链接数据并分析数据。
以上就是关于python爬虫之遍历单个域名的全部知识点,感谢大家的学习和对毛票票的支持。