java爬虫抓取动态网页(深入剖析Python爬虫框架的结构与运作流程的相关内容)
优采云 发布时间: 2021-12-07 19:06java爬虫抓取动态网页(深入剖析Python爬虫框架的结构与运作流程的相关内容)
想了解深入解析Python爬虫框架Scrapy的结构和运行过程吗?在本文中,pluskid将为大家仔细讲解Scrapy框架的结构和运行过程以及一些代码示例。欢迎阅读指正,先重点说一下:Python、Scrapy、爬虫,一起学习。
网络爬虫(Spider)是一种在互联网上爬行的机器人。当然,它通常不是实体机器人,因为网络本身也是一个虚拟的东西,所以这个“机器人”其实就是一个程序,不是爬行,而是有一定的用途,爬行的时候会采集. 一些信息。例如,谷歌有很多爬虫采集网页内容和它们之间的链接信息;另一个例子是别有用心的爬虫在互联网上采集诸如foo [at] bar [dot] com之类的东西。此外,还有一些定制的爬虫,专门针对某个网站。比如JavaEye的Robbin前段时间写了几篇专门对付恶意爬虫的博客。是的)和 网站 诸如小众软件或 LinuxToy 之类的软件通常会被整个站点抓取下来并以不同的名称挂出。其实,爬虫的基本原理非常简单。只要能上网,能分析网页,现在大多数语言都有方便的Http客户端库可以抓取网页,最简单的HTML分析可以直接使用正则规则。表达式做到了,所以做最简单的网络爬虫其实是一件很简单的事情。然而,要实现一个高质量的蜘蛛是非常困难的。表达式做到了,所以做最简单的网络爬虫其实是一件很简单的事情。但是,要实现一个高质量的蜘蛛是非常困难的。表达式做到了,所以做最简单的网络爬虫其实是一件很简单的事情。但是,要实现一个高质量的蜘蛛是非常困难的。
爬虫的两部分是下载网页。需要考虑的问题有很多,比如如何最大限度地利用本地带宽,如何调度不同站点的web请求,以减少其他服务器的负担。在高性能的Web Crawler系统中,DNS查询也将成为亟待优化的瓶颈。此外,还有一些“配置文件”需要遵循(例如,robots.txt)。获取网页后的分析过程也很复杂。网上奇奇怪怪的东西很多,各种HTML页面出现各种错误。分析所有这些几乎是不可能的。另外,随着AJAX的普及,如何获取Javascript动态生成的内容成为了一个大问题;此外,互联网上有各种有意或无意出现的蜘蛛陷阱。如果一味的跟着超链接走,就会被困在陷阱里。比如,这个网站据说是谷歌之前宣布互联网上Unique URL的数量已经达到1万亿,所以这个人很自豪的宣布第二万亿。:D
然而,实际上并没有多少人需要像谷歌这样的通用爬虫。通常我们构建一个爬虫来爬取一个特定的或者某个类型的网站,所谓知己知彼,百战不死,我们可以提前爬取对网站做一些分析网站 结构,事情变得容易多了。通过分析和选择有价值的链接进行跟踪,我们可以避免很多不必要的链接或蜘蛛陷阱。如果网站的结构允许选择合适的路径,我们就可以按照感兴趣的事物的一定顺序再次爬取它,这样就连URL重复的判断都可以省略了。
比如我们要爬下pongba博客中的博客文字,通过观察,很容易发现我们对其中两个页面感兴趣:
文章列表页面,比如首页,或者URL为/page/\d+/的页面,通过Firebug可以看到每个文章链接都在h1下的一个标签中(应该是注意Firebug的HTML面板看到的HTML代码可能和View Source看到的有些不同,如果网页中有动态修改DOM树的Javascript,前者是修改后的版本,Firebug正则化后,比如属性有引号等等,后者通常是你的蜘蛛爬取的原创内容,如果你用正则表达式分析页面或者使用的HTML Parser和Firefox有些不同,(需要特别注意). 另外,在一个类为 wp-pagenavi 的 div 中有指向不同列表页面的链接。
文章 内容页,每个博客都有这样一个页面,比如/2008/09/11/machine-learning-and-ai-resources/,收录了文章的完整内容,这是我们的感受感兴趣的内容。
因此,我们从首页开始,利用 wp-pagenavi 中的链接来获取其他 文章 列表页面。特别是我们定义了一个路径:只跟随Next Page的链接,这样我们就可以从头到尾依次进行一遍,省去了反复爬行判断的麻烦。另外,文章列表页上链接到具体文章的链接对应的页面就是我们真正要保存的数据页。
这样,它实用的脚本语言写一个ad hoc爬虫来完成这个任务并不难,不过今天的主角是Scrapy,它是一个用Python编写的爬虫框架,简单轻量,非常方便,官网说已经在实际生产中使用过,所以不是玩具级别的东西。但是,目前还没有 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如下:
from scrapy.spider import BaseSpider
class MindhacksSpider(BaseSpider):
domain_name = "mindhacks.cn"
start_urls = ["http://mindhacks.cn/"]
def parse(self, response):
return []
SPIDER = MindhacksSpider()
我们的MindhacksSpider继承自BaseSpider(通常直接来自scrapy.contrib.spiders.CrawlSpider,它更通用,更方便,但是为了展示数据是如何解析的,这里使用了BaseSpider),变量domain_name和start_urls是容易理解是什么意思,parse方法就是我们需要定义的回调函数。默认请求在得到响应后会调用这个回调函数。这里我们需要解析页面,返回两个结果(需要进一步爬取链接,需要保存Data)。让我觉得有点奇怪的是,它的接口定义中的两个结果居然混在一个列表中返回。我不知道为什么它是这样设计的。最后不是要把他们分开吗?总之,这里我们先写一个空函数,它只返回一个空列表。另外,定义一个“全局”变量 SPIDER,它会在 Scrapy 导入这个模块时被实例化,并且会被 Scrapy 引擎自动找到。所以你可以先运行爬虫试试:
./scrapy-ctl.py crawl mindhacks.cn
会有一堆输出,可以看到爬取了,因为这是初始的URL,但是因为我们在parse函数中没有返回需要进一步爬取的URL,所以整个爬取过程只爬取了首页和结束了。下一步是分析页面。Scrapy 提供了一个非常方便的Shell(需要IPython),可以让我们做实验。使用以下命令启动 Shell:
./scrapy-ctl.py shell http://mindhacks.cn
它会启动爬虫,抓取命令行指定的页面,然后进入shell。根据提示,我们有很多现成的变量可以使用。其中之一是 hxs,它是一个 HtmlXPathSelector。mindhacks 的 HTML 页面是比较标准的。直接用XPath分析非常方便。通过Firebug可以看到每个博客文章的链接都在h1下,所以在Shell中使用这个XPath表达式测试:
In [1]: hxs.x('//h1/a/@href').extract()
Out[1]:
[u'http://mindhacks.cn/2009/07/06/why-you-should-do-it-yourself/',
u'http://mindhacks.cn/2009/05/17/seven-years-in-nju/',
u'http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/',
u'http://mindhacks.cn/2009/03/15/preconception-explained/',
u'http://mindhacks.cn/2009/03/09/first-principles-of-programming/',
u'http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/',
u'http://mindhacks.cn/2009/02/09/writing-is-better-thinking/',
u'http://mindhacks.cn/2009/02/07/better-explained-conflicts-in-intimate-relationship/',
u'http://mindhacks.cn/2009/02/07/independence-day/',
u'http://mindhacks.cn/2009/01/18/escape-from-your-shawshank-part1/']
这正是我们需要的 URL。另外可以找到“下一页”的链接,和其他几个页面的链接放在一个div里,但是“下一页”的链接没有title属性,所以写XPath
//div[@class="wp-pagenavi"]/a[not(@title)]
但是,如果你往回翻一页,你会发现“上一页”其实是一样的,所以你需要确定链接上的文字是下一页的箭头u'\xbb',这可能有是用 XPath 编写的。去,不过好像这本身就是一个unicode转义字符,由于编码原因不清楚,直接在外面判断,最终解析函数如下:
def parse(self, response):
items = []
hxs = HtmlXPathSelector(response)
posts = hxs.x('//h1/a/@href').extract()
items.extend([self.make_requests_from_url(url).replace(callback=self.parse_post)
for url in posts])
page_links = hxs.x('//div[@class="wp-pagenavi"]/a[not(@title)]')
for link in page_links:
if link.x('text()').extract()[0] == u'\xbb':
url = link.x('@href').extract()[0]
items.append(self.make_requests_from_url(url))
return items
前半部分是解析需要爬取的博客正文的链接,后半部分是给出“下一页”的链接。需要注意的是,这里返回的列表并非都是字符串格式的URL。Scrapy希望得到的是Request对象,它可以携带比字符串格式的URL更多的东西,比如cookies或者回调。功能等。可以看到我们在创建博客正文的请求时替换了回调函数,因为默认的回调函数parse是专门用来解析文章列表等页面的,parse_post定义如下:
def parse_post(self, response):
item = BlogCrawlItem()
item.url = unicode(response.url)
item.raw = response.body_as_unicode()
return [item]
这很简单。返回一个 BlogCrawlItem 并将捕获的数据放入其中。你可以在这里做一些分析。比如你可以通过XPath来解析文本和标题,但是我倾向于后期做这些事情,比如Item Pipeline或者Later Offline stage。BlogCrawlItem 是 Scrapy 自动为我们定义的一个空类,继承自 ScrapedItem。在 items.py 中,我在这里添加了一些东西:
from scrapy.item import ScrapedItem
class BlogCrawlItem(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 的内容替换为以下代码:
import sqlite3
from os import path
from scrapy.core import signals
from scrapy.xlib.pydispatch import dispatcher
class SQLiteStorePipeline(object):
filename = 'data.sqlite'
def __init__(self):
self.conn = None
dispatcher.connect(self.initialize, signals.engine_started)
dispatcher.connect(self.finalize, signals.engine_stopped)
def process_item(self, domain, item):
self.conn.execute('insert into blog values(?,?,?)',
(item.url, item.raw, unicode(domain)))
return item
def initialize(self):
if path.exists(self.filename):
self.conn = sqlite3.connect(self.filename)
else:
self.conn = self.create_table(self.filename)
def finalize(self):
if self.conn is not None:
self.conn.commit()
self.conn.close()
self.conn = None
def create_table(self, filename):
conn = sqlite3.connect(filename)
conn.execute("""create table blog
(url text primary key, raw text, domain text)""")
conn.commit()
return conn
在__init__函数中,使用dispatcher将两个信号连接到指定的函数,用于初始化和关闭数据库连接(记得关闭前commit,好像不会自动commit,如果直接关闭的话,似乎所有数据都是 Dd-.-) 丢失。当数据通过管道时,将调用 process_item 函数。这里我们将原创数据直接存入数据库,不做任何处理。如有必要,您可以添加额外的管道来提取和过滤数据,但我不会在这里详细介绍。
最后,在 settings.py 中列出我们的管道:
ITEM_PIPELINES = ['blog_crawl.pipelines.SQLiteStorePipeline']
再次运行爬虫就OK了!
PS1:Scrapy 组件
1.Scrapy 引擎(Scrapy 引擎)
Scrapy 引擎用于控制整个系统的数据处理流程并触发事务处理。更多详细信息,请参见以下数据处理流程。
2.调度器(调度器)
调度器接受来自 Scrapy 引擎的请求并将它们排序到队列中,并在 Scrapy 引擎发送请求后将它们返回给它们。
3.下载器(下载器)
下载器的主要职责是抓取网页并将网页内容返回给蜘蛛。
4.蜘蛛(蜘蛛)
Spiders 是 Scrapy 用户定义的类,用于解析网页并抓取指定 URL 返回的内容。每个蜘蛛可以处理一个域名或一组域名。换句话说,它用于定义特定的网站 爬取和解析规则。
5.项目管道(项目管道)
项目管道的主要职责是处理蜘蛛从网页中提取的项目。它的主要任务是澄清、验证和存储数据。当页面被蜘蛛解析后,会被发送到项目管道,数据会按照几个特定的顺序进行处理。每个项目管道的组件都是具有简单方法的 Python 类。他们获得项目并执行他们的方法,他们还需要确定是否需要继续进行项目管道的下一步,或者干脆将其丢弃而不进行处理。
项目管道通常执行的过程是:
清理HTML数据,验证解析数据(检查item是否收录必要的字段) 检查数据是否重复(如果重复则删除) 将解析数据存入数据库
6.中间件(Middleware)
中间件是 Scrapy 引擎和其他组件之间的一个钩子框架,主要是提供自定义代码来扩展 Scrapy 的功能。
PS2:Scrapy的数据处理流程
Scrapy整个数据处理流程由Scrapy引擎控制,其主要运行方式为:
当引擎打开一个域名时,蜘蛛会对该域名进行处理,并要求蜘蛛获取爬取的第一个 URL。
引擎从蜘蛛那里获取第一个需要爬取的URL,然后在调度中作为请求进行调度。
引擎从调度程序获取接下来要抓取的页面。
调度器将下一个爬取到的URL返回给引擎,引擎通过下载中间件发送给下载器。
当下载器下载网页时,响应内容通过下载中间件发送给引擎。
引擎接收下载器的响应,通过蜘蛛中间件发送给蜘蛛进行处理。
蜘蛛处理响应并返回爬取的项目,然后向引擎发送新请求。
引擎将抓取项目管道并向调度员发送请求。
系统重复第二部分之后的操作,直到调度中没有请求,然后断开引擎和域之间的连接。
相关文章