scrapy分页抓取网页( WebCrawler下载Web页面如何调度站点的?)
优采云 发布时间: 2022-01-11 02:13scrapy分页抓取网页(
WebCrawler下载Web页面如何调度站点的?)
深度解析Python的爬虫框架Scrapy的结构和运行过程
网络爬虫(Web Crawler,Spider)是一种在互联网上爬行的机器人。当然,它通常不是实体机器人,因为网络本身也是一个虚拟的东西,所以这个“机器人”其实是一个程序,它不是爬行,而是有一定的目的,爬行时会采集数据。一些信息。例如,谷歌有大量的爬虫,它们在互联网上采集网页内容和它们之间的链接等信息;比如一些别有用心的爬虫,会在网上搜集foo[at]bar[dot]com等东西。另外,还有一些自定义爬虫,专门针对某个网站,比如JavaEye的Robbin前段时间写了几篇专门处理恶意爬虫的博客(原来的链接好像过期了,所以没有给出),而网站比如小众软件或者LinuxToy经常被全站爬下来,挂在不同的名字下。其实爬虫在基本原理上很简单,只要能上网分析网页,现在大部分语言都有方便的可以爬取网页的Http客户端库,以及最简单的HTML分析可以直接使用。表达式来做,所以做最基本的网络爬虫其实是一件很简单的事情。但是要实现一个高质量的蜘蛛是非常困难的。现在大部分语言都有方便的Http客户端库,可以爬取网页,最简单的HTML解析可以直接使用。表达式来做,所以做最基本的网络爬虫其实是一件很简单的事情。但是要实现一个高质量的蜘蛛是非常困难的。现在大部分语言都有方便的Http客户端库,可以爬取网页,最简单的HTML解析可以直接使用。表达式来做,所以做最基本的网络爬虫其实是一件很简单的事情。但是要实现一个高质量的蜘蛛是非常困难的。
爬虫的两个部分,一个是下载网页,有很多问题需要考虑,如何最大限度地利用本地带宽,如何调度不同站点的Web请求以减轻对方服务器的负担等等。在高性能的网络爬虫系统中,DNS查询也将成为需要优化的瓶颈。此外,还有一些“规则”需要遵守(例如 robots.txt)。得到网页后的分析过程也很复杂。网上各种奇葩,各种错误的HTML页面。几乎不可能全部分析。另外,随着 AJAX 的普及,如何获取 Javascript 动态生成的内容也成了一大难题;此外,互联网上有意无意地出现了各种各样的蜘蛛陷阱。如果你盲目地跟随超链接,你就会陷入陷阱,注定要失败。比如这个网站,据说谷歌之前宣布互联网上Unique URLs的数量已经达到1万亿,所以这个人很自豪地宣布了第二个万亿。:D
然而,实际上,像谷歌这样需要制作通用爬虫的人并不多。通常,我们制作一个 Crawler 来抓取特定的或某种类型的 网站。对 网站 结构进行一些分析,事情就会变得容易得多。通过分析和选择有价值的链接进行跟踪,可以避免许多不必要的链接或蜘蛛陷阱。如果 网站 的结构允许选择合适的路径,我们可以将感兴趣的事物按一定的顺序排列。再爬一遍,这样连URL重复的判断都可以省略。
比如我们要爬取pongba的博客中的博客文字,通过观察,很容易发现我们对两类页面感兴趣:
文章列出页面,比如首页,或者像/page/\d+/这样的URL的页面,通过Firebug可以看到每个文章的链接都在一个h1下的a标签中(需要注意的是,您在Firebug的HTML面板中看到的HTML代码可能与您在View Source中看到的有些出入。如果网页中存在动态修改DOM树的Javascript,则前者是修改版,并且Firebug正则化后,比如属性有引号等,而后者通常是你的蜘蛛爬取的原创内容。不同,需要特别注意),另外,wp-pagenavi类的一个div中还有指向不同列表页面的链接。
文章内容页面,每个博客都有这样一个页面,比如/2008/09/11/machine-learning-and-ai-resources/,里面收录了完整的文章内容,这是我们的感受感兴趣的内容。
因此,我们从首页开始,利用wp-pagenavi中的链接获取其他文章列表页面。特别是我们定义了一个路径:只跟随下一页的链接,这样就可以从头到尾按顺序再一次,免去了反复抓取判断的麻烦。另外,文章列表页面上具体文章的链接对应的页面,才是我们真正要保存的数据页面。
这样一来,用脚本语言编写一个ad hoc Crawler来完成这个任务并不难,但是今天的主角是Scrapy,它是一个用Python编写的Crawler Framework,简单、轻量、非常方便,而且官网说已经在生产中使用了,所以不是玩具级别的东西。不过目前还没有Release版本,可以直接使用他们的Mercurial仓库获取源码进行安装。不过这个东西也可以不用安装直接使用,方便随时更新。文档很详细,我不再赘述。
Scrapy 使用 Twisted(一个异步网络库)来处理网络通信。架构清晰,收录各种中间件接口,可灵活满足各种需求。整体结构如下图所示:
绿线是数据流向。从初始 URL 开始,Scheduler 会将其交给 Downloader 进行下载。下载后会交给Spider进行分析。Spider分析出来的结果有两个:一个是需要进一步爬取的链接,比如前面分析的“下一页”链接,这些东西会发回Scheduler;另一个是需要保存的数据,它们被发送到Item Pipeline,这是对数据的后处理(详细分析,过滤,存储等)。此外,可以在数据流通道中安装各种中间件,进行必要的处理。
具体内容将在最后的附录中介绍。
看起来很复杂,其实用起来很简单,就像Rails一样,先新建一个项目:
scrapy-admin.py startproject blog_crawl
会创建一个blog_crawl目录,里面有scrapy-ctl.py是整个项目的控制脚本,代码都放在子目录blog_crawl中。为了能够爬取,我们在spiders目录下新建了mindhacks_spider.py,定义我们的spider如下:
fromscrapy.spiderimportBaseSpider
类MindhacksSpider(BaseSpider):
域名=""
start_urls=[""]
解析(自我,响应):
返回[]
SPIDER=MindhacksSpider()
我们的MindhacksSpider继承自BaseSpider(通常直接继承自scrapy.contrib.spiders.CrawlSpider会更方便,功能更强大,但是为了展示数据是如何解析的,这里使用BaseSpider),变量domain_name和start_urls很容易理解是什么意思,parse方法就是我们需要定义的回调函数。默认请求在得到响应后会调用这个回调函数。我们这里需要解析页面,返回两个结果(需要进一步爬取的链接和需要保存的数据),让我感觉有点奇怪的是,在它的接口定义中,这两个结果是混合并返回一个列表。总之,这里我们先写一个空函数,它只返回一个空列表。此外,定义一个“全局” 变量 SPIDER,在 Scrapy 导入该模块时会被实例化,并被 Scrapy 的引擎自动找到。所以可以先运行爬虫试试:
./scrapy-ctl.py 抓取
会有一堆输出,可以看到爬取了,因为这是初始的URL,但是由于我们在parse函数中没有返回需要进一步爬取的URL,所以整个爬取过程只爬取首页并结束。下一步是分析页面。Scrapy 提供了一个非常方便的 Shell(需要 IPython),可以让我们做实验。使用以下命令启动 Shell:
./scrapy-ctl.py 外壳
它会启动爬虫,抓取命令行指定的页面,然后进入shell。根据提示,我们有很多现成的变量可以使用,其中一个就是hxs,它是一个HtmlXPathSelector。mindhacks 的 HTML 页面比较规范,可以直接用 XPath 分析,非常方便。从 Firebug 可以看出,每个博客 文章 的链接都在 h1 下,所以在 shell 中使用这个 XPath 表达式测试:
在[1]中:hxs.x('//h1/a/@href').extract()
输出[1]:
[你'',
你'',
你'',
你'',
你'',
你'',
你'',
你'',
你'',
你'']
这正是我们需要的 URL。此外,我们还可以找到“下一页”链接,它与其他几个页面的链接一起在一个 div 中,但是“下一页”链接没有标题属性,所以 XPath 读取
//div[@class="wp-pagenavi"]/a[not(@title)]
但是,如果你往回翻一页,你会发现“上一页”是一样的,所以需要判断链接上的文字是下一页的箭头u'\xbb',也可以是用 XPath 编写。Go,但似乎这本身就是一个 unicode 转义字符。由于编码原因,不清楚,所以直接在外面判断。最终的解析函数如下:
解析(自我,响应):
项目=[]
hxs=HtmlXPathSelector(响应)
帖子=hxs.x('//h1/a/@href').extract()
items.extend([self.make_requests_from_url(url).replace(callback=self.parse_post)
forurlinposts])
page_links=hxs.x('//div[@class="wp-pagenavi"]/a[not(@title)]')
forlinkinpage_links:
iflink.x('text()').extract()[0]==u'\xbb':
url=link.x('@href').extract()[0]
items.append(self.make_requests_from_url(url))
退换货品
前半部分是解析需要爬取的博客正文的链接,后半部分是给出“下一页”链接。需要注意的是,这里返回的列表不收录字符串格式的 URL。Scrapy 期望得到一个 Request 对象,它可以携带比字符串格式的 URL 更多的东西,例如 cookie 或回调。功能之类的。可以看出我们在创建博客正文的Request时替换了回调函数,因为默认的回调函数parse专门用于解析文章列表等页面,parse_post定义如下:
defparse_post(自我,响应):
项目=博客爬虫项目()
item.url=unicode(response.url)
item.raw=response.body_as_unicode()
归还物品]
很简单,返回一个BlogCrawlItem,把抓到的数据放进去。我们这里可以做一点分析,比如通过 XPath 解析文本和标题,但我倾向于在后面做这些事情,比如 Item Pipeline 或 Late Offline 阶段。BlogCrawlItem 是 Scrapy 自动为我们定义并继承自 ScrapedItem 的空类。在 items.py 中,我在这里添加了一些内容:
fromscrapy.itemimportScrapedItem
classBlogCrawlItem(ScrapedItem):
def__init__(self):
ScrapedItem.__init__(self)
self.url=''
def__str__(self):
return'BlogCrawlItem(url: %s)'%self.url
定义了__str__函数,只给出了url,因为默认的__str__函数会显示所有数据,所以看到爬取的时候,控制台日志会输出一些疯狂的东西,就是输出抓取到的网页内容。出去。-.-bb
这样就得到了数据,最后只剩下存储数据的功能了。我们通过添加管道来实现这一点。由于 Python 在标准库中自带了对 Sqlite3 的支持,所以我使用 Sqlite 数据库来存储数据。将 pipelines.py 的内容替换为以下代码:
导入sqlite3
fromos导入路径
来自scrapy.coreimportsignals
fromscrapy.xlib.pydispatchimportdispatcher
类SQLiteStorePipeline(对象):
文件名='data.sqlite'
def__init__(self):
self.conn=无
dispatcher.connect(self.initialize,signals.engine_started)
dispatcher.connect(self.finalize,signals.engine_stopped)
defprocess_item(自我,域,项目):
self.conn.execute('插入博客值(?,?,?)',
(item.url, item.raw,unicode(domain)))
归还物品
定义(自我):
ifpath.exists(self.filename):
self.conn=sqlite3.connect(self.filename)
别的:
self.conn=self.create_table(self.filename)
最终确定(自我):
ifself.connisnotNone:
mit()
self.conn.close()
self.conn=无
defcreate_table(自我,文件名):
conn=sqlite3.连接(文件名)
conn.execute("""创建表博客
(url 文本主键、原创文本、域文本)""")
mit()
返回连接
在__init__函数中,使用dispatcher将两个信号连接到指定函数,分别用于初始化和关闭数据库连接(记得close之前commit,好像不会自动commit,如果直接close的话,似乎所有数据都丢失了dd-.-)。当有数据通过管道时,将调用 process_item 函数。这里我们直接将原创数据存入数据库,不做任何处理。如有必要,您可以添加额外的管道来提取、过滤等数据,此处不再详细讨论。
最后,在 settings.py 中列出我们的管道:
ITEM_PIPELINES = ['blog_crawl.pipelines.SQLiteStorePipeline']
再次运行爬虫,就OK了!
PS1:Scrapy 的组件
1.Scrapy 引擎
Scrapy引擎用于控制整个系统的数据处理流程,触发事务处理。有关详细信息,请参阅下面的数据处理流程。
2.调度器
调度程序接受来自 Scrapy 引擎的请求并将它们排队并在 Scrapy 引擎发出请求后返回它们。
3.下载器
下载器的主要职责是抓取网页并将网页内容返回给蜘蛛。
4.蜘蛛
Spider是Scrapy用户定义的一个类,用于解析网页并爬取指定URL返回的内容。每个蜘蛛可以处理一个域名或一组域名。也就是说,它是用来定义具体的网站获取和解析规则的。
5.项目管道
项目管道的主要职责是处理蜘蛛从网页中提取的项目,其主要任务是澄清、验证和存储数据。当页面被蜘蛛解析时,它被发送到项目管道,并按几个特定的顺序处理数据。每个项目管道的组件都是具有一种简单方法的 Python 类。他们获取项目并执行他们的方法,同时还决定是否需要继续进行项目管道中的下一步或将其丢弃。
项目管道通常执行以下过程:
清理 HTML 数据 验证解析的数据(检查项目是否收录必要的字段) 检查重复数据(如果重复则删除) 将解析的数据存储在数据库中
6.中间件
中间件是 Scrapy 引擎与其他组件之间的钩子框架,主要是提供自定义代码来扩展 Scrapy 的功能。
PS2:Scrapy的数据处理流程
Scrapy的整个数据处理过程由Scrapy引擎控制,其主要操作方式有:
引擎打开一个域名,爬虫处理域名,让爬虫获取第一个爬取的URL。
引擎从蜘蛛获取第一个需要抓取的 URL,然后将其作为请求调度。
引擎从调度程序获取要抓取的下一页。
调度器将下一次爬取的 URL 返回给引擎,引擎通过下载中间件将它们发送给下载器。
网页被下载器下载后,通过下载中间件将响应内容发送给引擎。
引擎接收到下载器的响应,通过蜘蛛中间件发送给蜘蛛进行处理。
蜘蛛处理响应并返回抓取的项目,然后向引擎发送新请求。
引擎将抓取项目的项目管道并向调度程序发送请求。
系统在第二步之后重复操作,直到调度中没有请求,然后断开引擎与域的连接。
谢谢收看