爬虫是用来爬取网站(或一组网站)上的信息。他们必须子类化scrapy.Spider并定义初始请求,可以选择怎样跟踪页面中的链接,以及如何解析下载的页面获取你想要的的数据。
这是我们第一个爬虫代码。保存在一个名字为quotes_spider.py的文件里,放在你的项目中tutorial/spiders文件夹下
import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" def start_requests(self): urls = [ 'http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/', ] for url in urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self, response): page = response.url.split("/")[-2] filename = 'quotes-%s.html' % page with open(filename, 'wb') as f: f.write(response.body) self.log('Saved file %s' % filename) 如你所见,我们的Spider子类 scrapy.Spider 并定义了一些属性和方法:
name:识别蜘蛛。它在项目中必须是唯一的,也就是说,您不能为不同的Spiders设置相同的名称。
start_requests():必须返回一个可迭代的请求(您可以返回一个请求列表或写一个generator函数),Spider将从这里开始爬行。这些初始化链接将随后被依次处理。
parse():一个被调用来处理每个请求的方法。response参数TextResponse是一个保存页面内容的实例,并且还有其他有用的方法来处理它。
该parse()方法通常解析响应,将爬取的数据提取成字典,并且还可以查找跟踪新的URL并创建新的请求(Request)。
你会看到像:
[ ... Scrapy log here ... ] 2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None) [s] Available Scrapy objects: [s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc) [s] crawler <scrapy.crawler.Crawler object at 0x7fa91d888c90> [s] item {} [s] request <GET http://quotes.toscrape.com/page/1/> [s] response <200 http://quotes.toscrape.com/page/1/> [s] settings <scrapy.settings.Settings object at 0x7fa91d888c10> [s] spider <DefaultSpider 'default' at 0x7fa91c8af990> [s] Useful shortcuts: [s] shelp() Shell help (print this help) [s] fetch(req_or_url) Fetch request (or URL) and update local objects [s] view(response) View response in a browser >>> 使用shell,您可以尝试 选择元素通过 使用 CSS处理这些响应 对象 : >>> response.css('title') [<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]运行response.css('title')返回的结果是一个名为list的对象 SelectorList,它表示一个Selector对象列表,这个列表包含XML / HTML元素, 并允许您进行进一步的查询来细分选择或提取数据。
要从上面的标题中提取文本,您可以执行以下操作:
>>> response.css('title::text').extract() ['Quotes to Scrape'] 这里有两件事要注意:一个是我们已经添加 ::text 到CSS查询中,这意味着我们只想直接在 <title> 元素内部选择文本元素 。如果我们没有指定 ::text ,我们将获得完整的标题元素,包括其标签: >>> response.css('title').extract() ['<title>Quotes to Scrape</title>'] 另一件事是调用 .extract()返回 的结果 是一个列表,这是因为我们正在处理一个 SelectorList 实例 。当你只是想要第一个结果,你可以这样做:
>>> response.css('title::text').extract_first() 'Quotes to Scrape' 或者你可以写: >>> response.css('title::text')[0].extract() 'Quotes to Scrape'
但是,当没有找到与下标匹配的元素时,.extract_first()避免因为IndexError而返回None。所以推荐使用.extract_first()
这里有一个教训:对于大多数爬虫代码,你希望它是有弹性的错误由于在页面上没有找到你想要的内容,以至于即使某些部分爬取失败,您至少可以获取一些数据。
除了extract()和 extract_first()方法之外,您还可以re()使用正则表达式提取:
>>> response.css('title::text').re(r'Quotes.*') ['Quotes to Scrape'] >>> response.css('title::text').re(r'Q\w+') ['Quotes'] >>> response.css('title::text').re(r'(\w+) to (\w+)') ['Quotes', 'Scrape'] 为了找到找到合适的CSS选择器去使用,你可以使用这个shell命令 view(response)打开响应页面在你的浏览器中 。您可以使用浏览器开发工具或扩展工具(如Firebug)
Selector Gadget也是一个很好的工具,可以快速找到可选的元素的CSS选择器,它可以在许多浏览器中运行。
XPath表达式非常强大,是Scrapy选择器的基础。实际上,CSS选择器在底层被转换为XPath。
虽然也许不像CSS选择器那么受欢迎,但XPath表达式提供更多的功能,因为除了导航结构之外,它还可以查看内容。使用XPath,您可以选择以下内容:选择包含文本“下一页”的链接。这使得XPath非常适合爬取任务,我们鼓励您学习XPath,即使您已经知道如何构建CSS选择器,这将使爬取更容易。
http://quotes.toscrape.com中的每个报价都由HTML元素表示,如下所示:
<div class="quote"> <span class="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span> <span> by <small class="author">Albert Einstein</small> <a href="/author/Albert-Einstein">(about)</a> </span> <div class="tags"> Tags: <a class="tag" href="/tag/change/page/1/">change</a> <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a> <a class="tag" href="/tag/thinking/page/1/">thinking</a> <a class="tag" href="/tag/world/page/1/">world</a> </div> </div> 我们来打开scrapy shell,试一下找出如何提取我们想要的数据:$ scrapy shell 'http://quotes.toscrape.com' 我们得到一个列表的选择器的报价HTML元素: >>> response.css("div.quote") 通过上面的查询返回的每个选择器都允许我们对其子元素运行进一步的查询。 让我们将第一个选择器分配给一个变量,以便我们可以直接在特定的qutoe运行我们的CSS选择器:
>>> quote = response 。css (“div.quote” )[ 0 ] 现在,让我们来提取 title , author和 tags 从quote使用 我们刚刚创建的对象 quote:
>>> title = quote.css("span.text::text").extract_first() >>> title '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”' >>> author = quote.css("small.author::text").extract_first() >>> author 'Albert Einstein' tag标签是字符串列表,我们可以使用该 .extract() 方法来获取所有这些:
>>> tags = quote.css("div.tags a.tag::text").extract() >>> tags ['change', 'deep-thoughts', 'thinking', 'world'] 已经弄清楚如何提取每一个位,我们现在可以遍历所有quotes里的元素,并把它们放在一个Python字典中:
>>> for quote in response.css("div.quote"): ... text = quote.css("span.text::text").extract_first() ... author = quote.css("small.author::text").extract_first() ... tags = quote.css("div.tags a.tag::text").extract() ... print(dict(text=text, author=author, tags=tags)) {'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'} {'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'} ... a few more of these, omitted for brevity >>>
scrapy crawl quotes -o quotes.json
You can also used other formats, like JSON Lines:
scrapy crawl quotes -o quotes.jl>>> response.css('li.next a::attr(href)').extract_first() '/page/2/' 让我们看看现在我们的蜘蛛修改为递归地跟随链接到下一页,并从中提取数据: import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('small.author::text').extract_first(), 'tags': quote.css('div.tags a.tag::text').extract(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: next_page = response.urljoin(next_page) yield scrapy.Request(next_page, callback=self.parse) 现在,在提取数据之后,该 parse() 方法会查找到下一页的链接,通过使用 urljoin() 方法构建完整的绝对URL (由于链接是相对的),并且向下一页产生一个新的请求,将其注册为回调以处理下一页的数据提取,并爬取所有页面。
您在这里看到的是Scrapy的以下链接机制:当您以回调方式生成请求时,Scrapy将安排该请求发送,并注册一个回调方法,以在该请求完成时执行。
使用它,您可以根据您定义的规则构建复杂的跟踪链接,并根据访问页面提取不同类型的数据。
在我们的示例中,它创建一个递归循环,跟随到所有到下一页的链接,直到它找不到一个方便的抓取博客,论坛和其他站点分页。
import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" start_urls = [ 'http://quotes.toscrape.com/page/1/', ] def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('span small::text').extract_first(), 'tags': quote.css('div.tags a.tag::text').extract(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: yield response.follow(next_page, callback=self.parse)
与scrapy.Request不同,response.follow直接支持相关URL - 无需调用urljoin。请注意
您也可以传递一个selector到response.follow而不是字符串; 该selector应该提取必要的属性:
for href in response.css('li.next a::attr(href)'): yield response.follow(href, callback=self.parse) 对于<a>元素,有一个快捷方式:response.follow自动使用它们的href属性。所以代码可以进一步缩短: for a in response.css('li.next a'): yield response.follow(a, callback=self.parse)注意
response.follow(response.css('li.next a'))是无效的,因为 response.css返回带有所有结果的选择器的列表样对象,而不是单个选择器。response.follow(response.css('li.next a')[0])是可以的
这个蜘蛛演示的另一个有趣的事情是,即使同一作者有许多quotes,我们也不用担心多次访问同一作者页面。默认情况下,Scrapy会将重复的请求过滤出已访问的URL,避免了由于编程错误导致服务器太多的问题。这可以通过设置进行配置 DUPEFILTER_CLASS。
scrapy crawl quotes -o quotes-humor.json -a tag=humor 这些参数传递给Spider 的 __init__ 方法并成为spider的默认属性。
在这个例子中,为tag参数提供的值将humor通过self.tag。您可以使用它来使您的蜘蛛仅使用特定标记提取quotes,并根据参数构建URL:
import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" def start_requests(self): url = 'http://quotes.toscrape.com/' tag = getattr(self, 'tag', None) if tag is not None: url = url + 'tag/' + tag yield scrapy.Request(url, self.parse) def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').extract_first(), 'author': quote.css('small.author::text').extract_first(), } next_page = response.css('li.next a::attr(href)').extract_first() if next_page is not None: yield response.follow(next_page, self.parse) 如果您将 tag=humor 参数传递给此蜘蛛,您会注意到它只会访问 humor 标记中的URL ,例如 http://quotes.toscrape.com/tag/humor 。