爬虫抓取 第三章

Chapter3.开始抓取

到目前为止,本书的之前的例子讲的是单个静态页面,用了一些人为的页面。在本章中,我们将开始寻找一些现实世界中的问题,用爬虫跨越多个页面,甚至多个多个站点。网络爬虫之所以被叫做网络爬虫是因为他们可以在整个网络上抓取,它的核心元素是递归。他们必须检索URL的整个页面,检查该页面的其他URL,并继续检索,循环往复。但是要注意,你可以抓取页面但是并不意味着你要那么做。之前例子中的网络爬虫工作很好是因为所有的数据都在一个页面上。随着正式使用网络爬虫,你必须非常认真的考虑你正在使用的带宽,并尽一切去确认是否有办法使得目标服务器的负载更容易。

遍历单个域名:

即使你没有听说过“六度维基百科”,那么你肯定听说过同名的“凯文贝肯效应”(任何两个人之间中间仅仅需要经过6个人就可以产生联系)。在这两个事情中,我们的目标是连接两个不大可能的题目(在第一中情况下,维基百科的文章连接到彼此,第二种情况为演员出现在不同的电影中)通过完全不超过6个连接(包括两个原始的题目)。例如,Eric Idle(埃里克 爱都)和Brendan Fraser(布兰登 弗舍)出现在铁骑惊魂中,布兰登 弗舍和Kevin Bacon(凯文 贝肯)出现在我呼吸的空气中。在这种情况中,埃里克和凯文之间是第三级别的连接。这本节中,我们将从“六度维基百科”的解决发现者开始。意思就是,我们将会从埃里克的页面通过一些连接点去访问凯文的页面。

什么是维基百科的服务器负载:

根据维基百科基金会(维基百科背后的上级组织),该网站的资源每秒钟接受2500次访问,超过99%的是访问维基百科的域名(见“维基图”的“访问量”部分)。因为浏览量的绝对数量,你的网络爬虫不会对维基百科的服务器负载产生任何明显的影响。然而,如果你运行本书中的代码或者你创建的网络爬虫去爬取维基百科,我建议你做一个税收的捐献,哪怕是几美元也能抵消你产生的网络负载并且提供更好的教育资源去帮助其他人。

你应该知道如何去写一个检索维基百科任意界面的Python脚本,并且生成网页上链接的列表:

from urllib.request import urlopen

from bs4 import BeautifulSoup

html = urlopen("http://en.wikipedia.org/wiki/Kevin_Bacon")

bsObj = BeautifulSoup(html)

for link in bsObj.findAll("a"):

if 'href' in link.attrs:

print(link.attrs['href'])

如果你你看一下链接产生的名单,你会发现,你所期望的文章是:“阿波罗13号”,“费城”,“艾美奖”等等。然而,还有一些我们不希望有的东西:

//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行代码来确定一个维基百科的内部链路是否链接到一个文章页面。不幸的是,他没有在尝试找到文章链接和非文章链接之前花更多的时间,否则他可能发现了这个窍门。如果你检查链接点到该文章页(相对于其他内部页),他们有三个相同的地方:

①他们都在div之中而且id是bodyContent

②这个URLs都不包含分号

③这些URLs都以/wiki/开始

我们可以利用这些规则小幅度的修改代码来检索仅需要的文章链接:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import re

html = urlopen("http://en.wikipedia.org/wiki/Kevin_Bacon")

bsObj = BeautifulSoup(html)

for link in bsObj.find("div", {"id":"bodyContent"}).findAll("a",

href = re.compile("^(/wiki/)((?!:).)*$")):

if 'href' in link.attrs:

print(link.attrs['href'])

如果你运行这个,你可以看到维基百科中所有关于凯文的文章的URL列表。当然,有一个寻找文章连接并且锁死的维基百科文章的的脚本虽然很有趣,但是并没有什么用。我们需要用借此代码把它转化成下面这些条件的东西:

①一个单一功能的函数,getLinks,它获取由/wiki/<Article_Name>组成的文章URL,并且列表形式返回所有的文章URL。

②一个主要调用getLinks的开始文章的函数,从返回列表中随机选择文章链接,并且继续调用getLinks,直到我们程序停止或者在新的页面中没有找到新的文章链接。

这里是完成这个的完整代码:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import datetime

import random

import re

random.seed(datetime.datetime.now())

def getLinks(articleUrl):

html = urlopen("http://en.wikipedia.org"+articleUrl)

bsObj = BeautifulSoup(html)

return bsObj.find("div", {"id":"bodyContent"}).findAll("a",

href=re.compile("^(/wiki/)((?!).)*&"))

links = getLinks("/wiki/Kevin_Bacon")

while len(links) > 0:

newArticle = links[random.ranint(0, len(links)-1)].attrs["href"]

print(newArticle)

links = getLinks(newArticle)

这个程序做的第一件事情就是,导入所需要的库,设定随机数生成器种子为当前系统时间。这个实际上保证了每次程序运行查找维基百科的文章时都有一个新的和有趣的随机路径。

伪随机数和随机种子:

在前面的例子中,为了实现随机变量维基百科页面的文章,我使用了Python的随机数生成器。然而,随机数应该谨慎使用。

尽管电脑在计算正确答案上面是很棒的,但是在做事情上面就很糟糕。基于此原因,随机数就是一个挑战。大多数随机数的算法力求产生均匀分布的,并且难以预测的数字序列,而且每个种子都需要在算法的最初得到应用。完全相同的种子每次都会产生相同的随机数列,所以我们使用了系统时间作为一个新随机序列的开始,新的随机数列。这个使得程序运行起来更激动人心。

出于好奇,Python的伪随机数生成器算法采用了马特塞特旋转演算法。它产生的随机数是难以预测并且均匀分布的,他是处理器密集型的。随机数很棒来的也不简单。接着,它定义了getLinks函数,从预先考虑饿维基百科的域名http://en.wikipedia.org中取得包含/wiki/...文章的URL。并且检索域名的HTML的BeautifulSoup对象。然后提取文章列表的标签链接,基于先前讨论的参数,并且返回它们。

代码的主要内容开始于设置文章链接的标签列表(链接变量),在初始页面上的链接列表:http://bit.ly/1KwqjU7。然后开始一个循环,寻找页面中的随机文章的链接标签,并且从中提取<href>属性,打印页面,然后从提取到的URL中得到新的链接列表。

当然,这里还有更多解决“维基百科的六度空间”问题而不仅仅是一个简单的建立网页爬虫一页一页的去遍历。我们还必须能够存储和分析得到的结果。对于解决这些问题的后续,参见第五章。

异常处理:

为了例子的代码简明起见,我们在示例代码中忽略了大部分的异常处理,要知道,会出现很多潜在的隐患:如果维基百科改变了bodyContent标签的名字?(代码将会崩溃)因此,尽管这些备受关注的例子的脚本可以运行了,自己写的代码应该比我们书中的代码需要更多的异常处理,回顾第一章关于异常处理的更多知识。

爬取整个网站:

在上一节中,我们通过随机游走的方式通过了网站,从链接到链接。但是,如果你需要系统的目录或者搜索每个页面上的网站?爬取整个网站,特别是大型的,这个密集存储过程最适合的应用就是使用数据库来存储。但是,我们可以在实际运行之前全面探测这些应用程序的行为。要了解更多有关在数据库上运行这些应用程序,请参见第5章。

暗网和深网

你可能听说过深网,暗网,或者隐藏网页,特别是在近来的媒体上。他们都是什么意思呢?深网只是网页的任何部分,但不是表面网页的一部分。网络的表面就是说被搜索引擎收录的。估计差异很大,但是几乎可以肯定的是深网组成了网络的90%。因为谷歌不能提交表单,发现网页并没有链接到一个顶级域名,或者调查robots.txt禁止的网站这些事情,保持了网络的相对较小。

暗网,也被称为地下网络或者暗英特网,完全是另外一种野兽。它运行在现有的网络基础设施上,但是,使用Tor客户端与HTTP顶层的运行应用协议,提供交换信息的安全通道。尽管可以爬取地下网络,就像你可以爬取任何网站一样,这样做是超出本书范围的东西。不像暗网,深网相对容易爬取。事实上,这个书有很多工具教你如何寻找爬取一些谷歌机器人不能到的地方的信息。因此,当抓取整个网站是有用的还是有害的呢?

网络爬虫遍历整个网站抓取了很多东西,包括:生成网站地图:几年前,我面临着一个问题:一个重要客户想要对重写网站做一个预估,但是不想给我们公司提供其目前的内容管理系统的内部结构,没有可以用到的公开网站地图。我使用了爬虫来覆盖整个网站,收集所有的内部链接,并组织页面转换成实际的文件夹结构。这让我很快找到了网站中我根本不知道存在的部分,精确的计算有多少页面设计的内容将要迁移。

收集数据:

我的另一个客户希望收集文章(故事,博客,新闻文章等),用来创建一个专门的搜索平台的一个工作原型。尽管这些网站抓取并不需要很详细,他们也需要相当的大(当时只有少数几个网站,我们是从中得到感兴趣的数据)。我曾经创建爬虫,递归遍历每个站点并在文章页面中收集数据一般的爬取网页的途径是先从一个顶层页(如主页),并寻求网页上的所有内部链接的列表。接着爬取这些每个链接,找额外的链接列表中的每一个,开始新一轮的爬取。显然,这是一种可以迅速增长爬取情况。如果每个网站都有个内部链接和网站有5页的深度(

一个典型的中规模网站的深度)。那么你需要抓取的网页数量,在你可以确定彻底覆盖了网站,或者100,000页。奇怪的是,虽然“5页深并且每页10个内部链接”是一个网站相当典型的尺寸,还是有很少数的网站有10W以上的网页。当然,究其原因,是因为绝大多数内部链接是重复的。

为了避免抓取同一网页两次,最为重要的是,所有的内部发现链路格式一致,并保持在一个运行列表便于查找。当程序正在运行,只有那些“新”的链表应该被抓取和检索额外的链接:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import re

pages = set()

def getLinks(pageUrl):

global pages

html = urlopen()

bsObj = BeautifulSoup(html)

for link in bsObj.findAll("a", href = re.compile("^(/wiki/)")):

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("")

为了获得网页抓取的工作的所有作用,我已经在前一个例子中放宽了“我们正在寻找的内部链接”的标准。而不是限制爬虫在文章的页面,他会寻找开头为/wiki/的所有链接,不管他们在哪个页面上,并且不管他们是否包含冒号。请记住,文章页面不包含冒号,但是文件上传页面,讨论页面等中的RUL包含冒号。

最初,getLinks被称为一个空的URL。这被叫做“头版维基百科”,只要空的URL在函数中被换http://en.wikipedia.org。然后,在第一页的每一个环节,通过迭代来检查是否在全局页面上(脚本已经遇到的一组页面)。如果不是,它被添加到列表,打印到屏幕,并且getLinks函数式递归调用。

一个递归警告:

这是一个在软件书籍中很少出现的警告,但我想你应该知道,如果运行长度够了,那么前面的程序肯定会奔溃。

Python有一个默认的递归限制是1000(程序可以调用自己多少次)。由于维基百科的网络链接非常庞大,这个程序必定打破这个递归限制然后停止,除非你设置一个递归计数器或者其他东西来阻止这个发生。

对于“平面”网站链接深度是少于1000的,这种方法非常好,但是有一定的不同寻常的例外。例如,我曾经遇到过一个网站,有个个产生博客帖子的内部链接的规则。这个规则是“在我们当前页面的URL上面追加一个/blog/title_of_blog.php”问题是,他们往URL追加/blog/title_of_blog.php时候URL已经有了/blog/。所以,网站只需

添加其他的/blog/在上面。最后,我的爬虫去的网站就像这样: /blog/blog/blog/blog…/blog/title_of_blog.php。最后,我不得不添加一个检查,以确保网址是不是太可笑,含有重复段的可能表明一个无限循环。但是,如果我在离开之前不检查让它运行一整夜,它很容易崩溃。

收集整个站点的数据:

当然,如果当网页爬虫所做的只是从一页跳到另一个页面时候,将是相当枯燥的。为了使他们非常有用,我们将在这里对网页做一些东西。让我们看一下如何建立一个收集标题的爬虫,第一段的内容,并且编辑该页面的链接(如果可用)。和往常一样,第一步是确定如何更好的做好这一点是在网站看一下页面并且确定一个格式。通过查看少数维基百科的包含有标题和没有标题的页面,如隐私策略页,接下来的事情就很明确了:

①所有标题(在所有网页上,不论是文章页面状态,编辑历史页面,或者其他网页)在h1 → span标签内有标题,这些仅仅是在页面的h1标签。

②正如前面所提到的,所有的body文本在 div#bodyContent标签下面。然而,我们希望得到更具体并且仅仅访问文本的第一段,我们可能更好的使用div#mw-content-text → ? p(仅仅选择第一段的标签)。这个对于所有的除了文件页面都是真实的(例如, http://bit.ly/1KwqJtE),不具有内容文本部分。

③编辑链接只出现在文章页。如果他们出现,他们将在出现在li#ca-edit标签,在li#ca-edit →? span →? a之下。

通过修改我们的基本爬取代码,我们可以创建一个爬取/数据收集(或者至少是数据打印)的组合程序:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import re

pages = set()

def getLinks(pageUrl):

global pages

html = urlopen()

bsObj= BeautifulSoup(html)

try:

print(bsObj.h1.get_text())

print(bsObj.find(id = "mw-content-text").findAll("p")[0])

print(bsObj.find(id = "ca-edit").find("span").find("a").attrs['href'])

except AttributeError;

print("This page is missing something! No worris though!")

for link in bsObj.findAll("a", href=re.compile("^(/wiki/)")):

if 'href' in link.attrs:

if link.attrs['href'] not in pages:

#We have encountered a new page

newPage = link.attrs['href']

print("---------------------\n"+newPage)

pages.add(newPage)

getLinks(newPage)

getLinks("")

for循环这一个程序中本质是与原来相同的,在原来爬取程序的基础上(加一下破折号是为了打印清楚,分离打印的内容)。因为我们永远无法完全肯定,所有的数据是在每个页面上的,每个打印语句是顺序排列的,这些是有可能出现在网站上的。即,<h1>标题标签出现在每个页面(据我所知,在任何概率上),所以我们第一步是尝试获取数据。文本内容出现在大多数页面(出文件页面),所以这是第二步数据的检索。“编辑”按钮仅出现在网页中当标题和文本内容同时存在时候,但她不会出现在所有的这些网页上。

不同的模式针对不同的需求:

有一些明显的危险藏在众多的异常处理程序中。一件事情就是你分不清哪行抛出异常。此外,由于某种原因,一个页面含有一个“编辑”按钮,但是没有标题,这个“编辑”按钮就永远不会被记录。然而,它足够用于许多出现在网站上的物品相似的顺序的实例,并且无意中缺少几个数据或者保持详细的日志是没有问题的。你可能会注意到,在这和前面所有的例子中,我们并没有“收集”数甚至于“打印”它。很明显,在终端数据是很难操纵的。我们将会在第5章看到更多的信息存储和创建数据库。

爬互联网:

每当我有一次关于网络爬虫的讨论,难免有人问:“你如何建立谷歌?”我的回答永远是双重的:“首先你会得到数十亿美元,这样你可以买到世界上最大的数据仓库,并放置在世界各地的隐蔽位置。第二,你建立一个网络爬虫。”

当谷歌1994年开始时候,这只是两位斯坦福的毕业生和旧的服务器以及一个Python网路爬虫。现在你知道这个,你有需要成为下一个高科技亿万富翁的正式工具!

严重的说,网络爬虫是驱使很多现代网络技术的心脏,你不一定需要一个大的数据仓库来使用他们。为了做任何跨域数据分析,你就需要建立一个可以在网络中无数的不同页面中解释和存储数据的爬虫。就像前面的例子中,我们建立的网络爬虫通过链接一页到一页,建立了网站地图。但是这一次,他们不会忽略外部链接;他们会跟着他们。对于一个额外的挑战,我们可以看到,如果我们记录各种关于我们通过的每个页面的信息。这将是比我们之前和单个域名工作更难的-不同的网站有着完全不同的布局。这意味着我们在寻找和如何寻找各种信息上面非常灵活。

未来未知的水域:

请记住,下一部分的代码可以运行在互联网的任何地方。如果我们从“六度维基百科”学会了什么,那就是完全可能从一个网站比如http://www.sesamestreet.org/去其他地方在短短几跳。孩子,运行前要先问你的父母。在实际运行中,对于那些敏感的宪法或宗教可能禁止在津津乐道的网站被看到,阅读示例代码要小心。你开始编写不管三七二十一的简单遵循跟随所有出站链接的爬虫,你应该问自己几个问题:

①我试图收集哪些信息?这可以通过一些预定义的网站完成(几乎是更容易的选择),还是我的爬虫需要发现一些新的我不知道的网站?

②当我的爬虫到达一个特定的网站,它会立即进入下一个外部链接到一个新的网站,还是坚持绕着当前网站一段时间并且深入到当前的网页中?

③在这些我不想爬取的特定网站下是否有一些条件?我是否对非英语的内容感兴趣?

④我如何在违反法律的行为中保护自己?如果我的网络爬虫抓取被爬去网站之一的站长关注?(查看附录C关于这个主题的更多信息)

Python函数的灵活性可以组合成多种不同类型的网络爬虫去运行,可以很轻易的写出少于50行代码:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import re

import datetime

import random

pages = set()

random.seed(datetime.datetime.now())

#Retrives a list of all Internal links found a page

def getInternalLinks(bsObj, includeUrl):

internalLinks = []

#find all links begin with a "/"

for link in bsObj.findAll("a", href = re.compile("^(/|.*"+includeUrl+")")):

if link.attrs['href'] is not None:

if link.attrs['href'] not in internalLinks:

internalLinks.append(link.attrs['href'])

return internalLinks

#Retrives a list of all external links found a page

def getExternalLinks(bsObj, excludeUrl):

externalLinks = []

#find all links that start with "http" or "www" that do

#not contain the current URL

for link in bsObj.findAll("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 splitAddress(address):

addressParts = address.replace("http://", "").split("/")

return addressParts

def getRandomExternalLink(startingPage):

html = urlopen(startingpage):

bsObj = BeautifulSoup(html)

externalLinks = getExternalLinks(bsObj, splitAddress(startingPage)[0])

if len(externalLinks) == 0:

internalLinks = getInternalLinks(startingPage)

return getNextExternalLink(internalLinks[random.randint(0,

len(internalLinks)-1)])

else:

return externalLinks[random.randint(0, len(externalLinks)-1)]

def followExternalOnly(startingSite):

externalLinks = getRandomExternalLink("http:oreilly.com")

print("Random external link is: "+externalLink)

folloeExternalOnly(externalLink)

followExternalOnly("http://oreilly.com")

前面的程序开始于http://oreilly.com,随机从外部链接跳向外部链接。下面是它产生的输出例子:

Random external link is: http://igniteshow.com/

Random external link is: http://feeds.feedburner.com/oreilly/news

Random external link is: http://hire.jobvite.com/CompanyJobs/Careers.aspx?c=q319

Random external link is: http://makerfaire.com/

外部链接并不总是保证能在网站的第一页找到。为了在这种情况下找到第一链接,类似于前面爬取例子用到的方法,如,采用递归深入到每一个网站,直到找到一个外部链接。

图3-1 可视化的操作流程图:

不要把示例程序投入生产:

我一直提出这个问题,这本书中的实例程序做的并不总是包含“生产—就绪”的代码那样必要的检查和异常处理,这样做对于空间和可读性很重要。例如,如果爬虫遇见在一个网页上面都没有找到任何的外部链接(可能性不大,但是你如果运行它足够长的时间那么必然会在某些时候发生),这个程序将继续运行,直到它遇到Python的递归限制。在任何重要的目的,运行这个代码之前,请确保你检查到位,已处理潜在的隐患。

关于分解任务到不同的函数,比如“在页面上寻找外部链接”就使得代码可以很容易的重构,以执行不同的爬取任务举例来说,如果我们的目标是抓取整个网站的外部链接,并且记下每一个,我们可以添加以下函数:

#Collets a list of all external URLs found on the site

allExtLinks = set()

allIntLinks = set()

def getAllExternalLinks(siteUrl):

html = urlopen(siteUrl)

bsObj = BeautifulSoup(html)

internalLinks = getInternalLinks(bsObj, splitAddress(siteUrl)[0])

externalLinks = getInternalLinks(bsObj, splitAddress(siteUrl)[0])

for link in externalLinks:

if link not in allExtLinks:

allExtLinks.add(link)

print(link)

for link in internalLinks:

if link not in allIntLinks:

print("About to get link:"+link)

allIntLinks.add(link)

getAllExternalLinks(link)

getAllExternalLinks("http://oreilly.com")

这个代码可以被看做两个循环——一个收集内部链接,一个收集外部链接——两者相互配合工作。这个流程看起来如图3-2:

在写代码之前记下或者做出流程图本身是一个很棒的习惯,并且可以为你节省大量的时间和避免大量挫折,使得爬虫更复杂。

处理重定向

重定向允许在不同域名下查看同一网页。重定向有两种形式:

①服务器端重定向,URL将会在页面被加载前改变

②客户端重定向,有时你会见到“你将会在10s内被重定向...”类型的引导信息,在页面被加载之前重定向到一个新的页面。

本节将处理服务器端重定向。对于客户端重定向的详细信息,会使用JavaScript或者HTML,参见第十章。

对于服务器端重定向,通常不必担心。如果你在Python3.x中使用urllib库,它会自动处理重定向!需要注意的是,有时候,你抓取页面的URL可能不是你输入在页面上的URL。

通过Scrapy抓取

编写网络爬虫的一个挑战是,经常一遍又一遍的执行相同的任务:找到一个页面上的所有链接,评定内部链接和外部链接之间的不同,进入新的页面。这些基本模式对于知道从头编写是很有用的,但是你如果需要一些处理其他东西的选项,这里有详细的介绍。

Scrapy是在一个网页上处理多复杂性的寻找和评估链接的一个Python库,轻松的爬取域名或者域名的列表。不幸的是,Scrapy还没有适配Python3.x版本,所以只兼容Python2.7版本。

好消息是,即使一台机器上面有多个版本的Python(比如,Python2.7和Python3.4)通常是工作正常的。如果你有一个项目想使用Scrapy,但也想使用其他Python3.4的脚本,你这么做是不会有问题的。

Scrapy网站提供下载工具,以及第三方的下载指令,比如pip。请记住,你需要使用Python2.7安装Scrapy(不是2.6或者3.x),所有的使用Scrapy的程序都使用Python2.7。

虽然写Scrapy爬虫是相对容易的,这里需要对每个爬虫做少量的设置。在当前目录下创建一个新的Scrapy项目,在命令中运行:

$scrapy startproject wikiSpider

wikiSpider是我们新项目的名称。这个在当前目录中创建了一个名叫wikiSpider的新目录,这个目录里面是以下文件结构:

scrapy.cfg

wikiSpider

__init.py__

items.py

pipelines.py

settings.py

spiders

__init.py__

为了创建一个爬虫,我们将在wikiSpider/wikiSpider/spiders/articleSpider.py中添加一个叫items.py的文件。此外,我们将在items.py中定义一个叫Artic的新项目。

你的items.py文件应该被编辑成这样(还剩Scrapy生成评论的地方,虽然你可以随意删除他们):

# -*- coding: utf-8 -*-

# Define here the models for your scraped items

#

# See documentation in:

# http://doc.scrapy.org/en/latest/topics/items.html

from scrapy import Item, Field

class Article(Item):

# define the field for your item here like:

# name = scrapy.Field()

title = Field()

每个Scrapy的Item对象表示网站上的一个页面。显然,正如你所想,你可以定义许多领域(url,content,header image等),但现在我只是简单的收集页面上的title字段:

在你新创建的articleSpider.py文件中,编写如下:

from scrapy.selector import Selector

from scrapy import Spider

from wikiSpider.items import Article

class ArticleSpider(Spider):

name = "article"

allowed_domains = ["en.wikipedia.org"]

start_urls = ["http://en.wikipedia.org/wiki/Main_Page",

"http://en.wikipedia.org/wiki/Python_%28programming_language%29"]

def parse(self, response):

item = Article()

title = response.xpath('//h1/text()').extract()

print("title is: "+title)

item['title'] = tltle

return item

这个对象的名称(ArticleSpider)与目录(WikiSpider)的名称不同,表明这个类特定的作用就是只负责爬虫通过文章页面,在更广泛的WikiSpider类别之下。对于大型网站有许多类型的内容,你可能有每个类型的Scrapy项目(博客,新闻发布,文章等),每一个不同的领域,但是都运行在相同的Scrapy项目之中。

你可以从WikiSpider的目录中输入运行这个ArticleSpider:

$ scrapy crawl article

这通过项目的名称article调用(不是累或者文件名,但是名字在ArticleSpider中定义:name = "artcle")。

随着一些调试信息,这应该打印出几行:

Title is:Main Page

Title is: Python(Program Language)

爬虫前进到为start_urls的两页,收集信息,然后终止。一个爬虫没有太大关系,但是如果你有一个列表的URL需要爬取,使用通过这种方式使用Scrapy会非常有用。把它变成一个完全成熟的爬虫,你需要定义一套规则,让Scrapy可以使用来在每个遇到的页面上找出新的URL:

from scrapy.contrib.spiders import CrawlSpider, Rule

from wikiSpider.items import Article

from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor

class ArticleSpider(CrawlSpider):

name = "article"

allowed_domains = [""]

start_urls = []

rules = [Rule(SgmlLinkExtractor(allow=('(/wiki/)((?!:).)*$'),),

callback="parse_item", folow=True)]

def parse_item(self, response):

item = Article()

title = response.xpath('//h1/text()')[0].extract()

print("Title is: "+title)

item['title'] = title

return item

这个爬虫和前一个使用相同的命令运行,但是他不会终止(至少不会是一个很长很长的时间),直到你使用Ctrl+C停止执行或者关闭中断。

Scrapy的日志

由Scrapy产生的调试信息是有用的,但是他往往过于冗长。你可以通过在Scrapy项目的setting.py文件中加入一行就可以轻松的调整日志级别:

LOG_LEVEl = 'ERROR'

这里有五个级别的日志记录,在这里列出:

①CRITICAL(关键)

②ERROR(错误)

③WARING(警告)

④DEBUG(调试)

⑤INFO(信息)

如果日志设置为ERROR,将只显示CRITCAL和ERROR日志。如果日志记录设置为INFO,所有日志都将被显示,等等。

日志输出一个单独的日志文件而不是中断,只需当在命令行运行时候定义日志文件:

$ scrapy crawl article -s LOG_FILE=wiki.log

如果日志不存在,这将在当前目录创建一个新的日志文件,并且输出和打印所有日志在里面。

Scrapy使用Item对象来确定访问页面的哪份信息应该被保存。这个信息可以通过Scrapy以各种方式保存,如CSV,JSON或者XML文件,使用下面的命令:

$ scrapy crawl article -o articles.csv -t csv

$ scrapy crawl article -o articles.json -t json

$ scrapy crawl article -o articles.xml -t xml

当然,你可以使用Item对象本身并且写入到一个文件或者数据库,任何你想的地方。只需要在爬虫中添加解析函数的相应代码。

Scrapy是处理与爬取网页相关联的许多问题的有力工具。它自动收集所有的网址,并比较他们与预定义的规则,确保所有的URL都是唯一的,在需要的地方相对标准化,以及递归去更多的页面。

虽然本节几乎从表面上触及了Scrapy可以做的,我建议你之外查看Scrapy文档或者其他在线资源。Scrapy是一个非常庞大的库拥有很多功能。如果这里有什么你想要通过Scrapy做但是没有提到的,有可能有一个(或者几个)做到这一点!



转载请注明出处 http://blog.csdn.net/qq_15297487

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值