XPath
全称XML Path Language ,即XML路径语言,它是一门在XML文档中查找信息的语言,最初是用来搜寻XML文档的,但是它同样适用于HTML文档的搜索。我们在做爬虫时,完全可以用XPath来做相应的信息抽取。
Xpath的选择功能非常强大,它提供了非常简明的路径选择表达式,另外,它还提供了超过00个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点,都可以用XPath来选择。
XPath常用的规则
nodename:选取此节点所有子节点
/:从当前节点选取直接子节点
//:从当前节点选取子孙节点
.选取当前节点
..选取当前节点的父节点
@ 选取属性
from lxml import etree
text='''
<div>
<ul>
<li class='item-0'> <a href='link1.html'>first item</a><li>
<li class='item-1'> <a href='link2.html'>second item</a><li>
<li class='item-inactive'> <a href='link3.html'>third item</a><li>
<li class='item-1'> <a href='link4.html'>fourth item</a><li>
<li class='item-0'> <a href='link5.html'>fifth item</a>
</ul>
</div>
'''
html=etree.HTML(text)
result=etree.tostring(html)
print(result.decode('utf-8'))
这里首先导入lxml库的etree模块,然后声明了一段HTML文本,调用HTML类进行初始化,这样就成功构造了一个XPath解析对象。这里需要注意的是,HTML文本中的最后一个li节点是没有闭合的,但是etree模块可以自动修正HTML文本。
这里我们调用tostring() 方法即可输出修正后的HTML代码,但是结果是bytes类型。这里用decode() 方法将其转成str类型
可以看到,经过处理之后,li节点标签被补全,并且还自动添加了body,html节点。
另外也可以直接读取文本文件进行解析
test.html里面的内容
<div>
<ul>
<li class='item-0'> <a href='link1.html'>first item</a><li>
<li class='item-1'> <a href='link2.html'>second item</a><li>
<li class='item-inactive'> <a href='link3.html'>third item</a><li>
<li class='item-1'> <a href='link4.html'>fourth item</a><li>
<li class='item-0'> <a href='link5.html'>fifth item</a>
</ul>
</div>
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('utf-8'))
所有节点
我们一般会用//开头的XPath规则来选取所有符合要求的节点。这里以上面的HTML文档为例,如果要选取所有节点,可以这样实现:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//*')
print(result)
这里使用*代表匹配所有节点,也就是整个HTML文档中所有节点都会被获取。可以看到,返回形式是一个列表,每个元素是Element类型,后跟了节点的名称。
当然,此处匹配也可以指定节点名称,如果想获取所有li节点,示例如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li')
print(result)
print(result[0])
这里可以看到提取结果是一个列表形式,其中每个元素都是一个Element对象。如果要取出其中一个对象,可以直接用中括号加索引,如[0]
子节点
我们通过/或//即可查找元素的子节点或子孙节点。假如现在想选择li节点的所有直接a子节点,可以这样实现
这里通过追加/a即选择了所有li节点的所有直接a子节点。因为//li用于选中所有li节点,/a用于选中li节点的所有子节点a,二者组合在一起即获取所有li节点的所有直接a子节点。
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a')
print(result)
print(result[0])
此处的/用于选取直接子节点,如果要获取所有子孙节点,就可以使用//。例如,要获取ul节点下的所有子孙a节点,就可以这样实现:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//ul//a')
print(result)
print(result[0])
但是如果用//ul/a。就无法获取任何结果了。这里/用于获取直接子节点,而在ul节点下没有直接的a子节点,只有里节点,所以就无法获取任何匹配结果,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//ul/a')
print(result)
print(result[0])
因此。这里要注意/和//的区别,其中/用于获取直接子节点,//用于获取子孙节点
父节点
查找父节点用..来实现
比如,现在首先选中href属性为link4.html的a节点,然后再获取其父节点,然后再获取其class属性,相关代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href="link4.html"]/../@class')
print(result)
print(result[0])
也可以通过parent::来获取父节点,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)
属性匹配
在选取的时候,我们还可以用@符号进行属性过滤。比如,这里如果要选取class为item-0的li节点,可以这样实现:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]')
print(result)
这里我们通过[@class="item-0"],限制了节点的 class属性为item-0,而HTML文本符合条件的li节点有两个,所以结果应该返回两个匹配到的元素。
是不是正确的那两个?
文本获取
首先使用XPath中的text()方法获取节点中的文本,接下来尝试获取前面li节点中的文本,相关代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]/text()')
print(result)
获取到换行符,因为XPath中的text()前面是/,而此处/的含义是选取直接子节点,很明显li的直接子节点都是a节点,文本都是在a节点内部的,所以这里匹配到的结果就是被修正的li节点内部的换行符,因为自动修正的li节点的尾部标签换行了。
其中一个节点因为自动修正,li节点的尾标签添加的时候换行了,所以提取文本得到的唯一结果就是li节点的尾标签和a节点的尾标签之间的换行符。
因此,如果想要获取li节点内部的文本,就有两种方式,一种是先选取a节点再获取文本,另一种就是使用//。接下来,我们来看下二者的区别。
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]/a/text()')
print(result)
内容都是属性为item-0的li节点的文本,这也印证了前面属性匹配的结果是正确的
这里我们是逐层选取的,现选取了li节点,又利用/选取了其直接子节点a,然后再选取其文本,得到的结果恰好是符合我们预期的两个结果。
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]//text()')
print(result)
这里返回的结果是三个,这里是选取所有子算节点的文本,其中前两个就是li的子节点a节点内部的文本,另外一个就是最后一个里节点内部的文本,即换行符
所以说,如果想要获取子孙节点内部的所有文本,可以直接用//加text()的方式,这样可以保证获取最全面的文本信息,但是可能会夹杂一些换行符等特殊字符。如果想要获取某些特定子孙节点下的所有文本,可以先选取到特定的子孙节点,然后再调用text()方法获取其内部文本,这样就可以保证获取的结果是整洁的。
属性获取
我们知道用text()可以获取节点内部文本,那么节点属性应该怎样获取?,其实还是用@符号就可以了,例如,我们想获取所有li节点下所有a节点的href属性,代码如下:
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a/@href')
print(result)
可以看到,我们成功获取了所有li节点下a节点的href属性,它们以列表形式返回。
Beautiful Soup
强大的解析工具Beautiful Soup,它借助网页的结构和属性等特性来解析网页,有了它,不用再去写一些复杂的正则表达式,只需要简单的几条语句,就可以网页某个元素的提取。
Beautiful Soup 在解析时实际上依赖,它除了支持python标准库中的html解析器外,还支持一些第三方解析器 用lxml解析器,使用lxml,在初始化Beautiful Soup 时,可以把第二个参数改为lxml即可
在后面统一使用lxml解析器来进行演示
import requests
from bs4 import BeautifulSoup
html='''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story"> Once upon a time there were three little sisters;and their names were
<a href="http"//example.com/elsie" class="sister" id="link1"><!--Elsie--></a>,
<a href="http"//example.com/lacie" class="sister" id="link2"><!--Lacie--></a> and
<a href="http"//example.com/tillie" class="sister" id="link3"> Tillie </a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
'''
soup=BeautifulSoup(html,'lxml')
print(soup.prettify())
print(soup.title.string)
这里首先声明变量html,它是一个HTML字符串,但是需要注意的时,它不是一个完整的HTML字符串,因为body和html节点都没有闭合。接着,我们将它当做第一个参数传给BeatutifulSoup对象,该对象的第二个参数为解析器的类型(这里使用lxml),此时就完成了BeautifulSoup对象的初始化,然后,将这个对象赋值给soup变量。
解析来,就可以调用soup的各个方法和属性解析这串HTML代码
首先,调用prettify()方法,这个方法可以把要解析的字符串以标准的缩进格式输出,这里需要注意的是,输出结果里面包含body和html节点,也就是说对于不标准的HTML字符串BeautifulSoup可以自动更正格式。这一步不是由prettify()方法做的,而是在初始化BeautifulSoup时就完成了
然后调用soup.title.string,这实际输出的时HTML中title节点的文本内容。所以,soup.title可以选出HTML中的title节点,再调用string属性就可以得到里面的文本了,所以我们可以通过点单调用几个属性就可以完成文本的提取,是不是非常方便。
节点选择器
直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本了,这种选择方式速度非常快,如果单个节点结构层次非常清晰,可以选用这种方式来解析、
import requests
from bs4 import BeautifulSoup
html='''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story"> Once upon a time there were three little sisters;and their names were
<a href="http"//example.com/elsie" class="sister" id="link1"><!--Elsie--></a>,
<a href="http"//example.com/lacie" class="sister" id="link2"><!--Lacie--></a> and
<a href="http"//example.com/tillie" class="sister" id="link3"> Tillie </a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
'''
soup=BeautifulSoup(html,'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
这里依然选用刚才的HTML代码,首先打印输出title节点的选择结果,输出结果时title节点加里面的文字内容。接下来,输出它的类型,时bs4.element.Tag类型,这是BeautifulSoup中一个重要的数据结构。经过选择器选择后,选择结果都是这种Tag类型,Tag具有一些属性,比如string属性,调用该属性,可以得到节点的文本内容,所以接下来的输出结果正是节点的文本内容。
接下来,我们又尝试选择head节点,结果又是节点加其内容的所有内容。最后,选择了p节点。不过结果是第一个p节点的内容,后面的几个p节点并没有找到。也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,其他的后面节点都会忽略。
提取信息
string属性获取文本的值,那么如何获取节点属性的值?
1 获取名称
可以利用name属性获取节点的名称,这里还是以上面文本为例子,选取title节点,然后调用name属性就可以得到节点名称
soup=BeautifulSoup(html,'lxml')
print(soup.title.name)
2 获取属性
每个节点可能有多个属性,比如id和class等,选择这个节点元素后,可以调用attrs获取所有属性:
soup=BeautifulSoup(html,'lxml')
print(soup.p.attrs)
print(soup.p.attrs['name'])
可以看到,attrs的返回结果是字典形式,它把选择的节点的所有属性和属性值组合成一个字典。接下来,如果要获取name属性,就相当于从字典中获取某个键值,只需要用中括号加属性名就可以了。比如,要获取name属性,就可以通过attrs['name']来得到。
其实这样有点烦琐,还有一种更简单的获取方式:可以不用写attrs,直接在节点元素后面加中括号,传入属性名就可以获取属性值
soup=BeautifulSoup(html,'lxml')
print(soup.p['name'])
print(soup.p['class'])
这里需要注意的是,有的返回的是字符串,有的返回结果是字符串组成的列表,比如name属性返回的值是唯一的,返回的结果就是单个字符串,而对于class,一个节点元素可能有多个class,所以返回的是列表
获取内容
可以利用string属性获取节点元素包含的文本内容,比如要获取第一个p节点的文本:
soup=BeautifulSoup(html,'lxml')
print(soup.p.string)
p节点是第一个p节点,获取的文本也是第一个p节点里面的文本
嵌套选择
在上面的例子中,我们知道每一个返回结果都是bs4.element.Tag类型,它同样可以继续调用节点进行下一步的选择,比如,我们获取了head节点元素,我们可以继续调用head来选取其内部的head节点元素
import requests
from bs4 import BeautifulSoup
html='''
<html><head><title>The Dormouse's story</title></head>
<body>
'''
soup=BeautifulSoup(html,'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
第一行结果是调用head之后再次调用title而选择title节点元素。然后打印输出了它的类型,可以看到,它仍然是bst.element.Tag类型。也就是说,我们在Tag类型基础上再次选择得到的依然是Tag类型,最后,输出它的string属性,也就是节点里的文本内容。
方法选择器
前面所讲选择方法都是通过属性来选择的,这种方法非常快,但是如果进行比较复杂的选择,就比较麻烦,因此bs还提供了一些查询方法,调用它们,然后传入相应参数就可以了
find_all(name,attrs,recursive,text,**kwargs)
1name
可以根据节点名来查询元素
html="""
<div class="panel">
<div class="panel-heading">
<h4> hello <h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">jAY</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element"> Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
调用find_all()方法,传入name参数,参数值为ul也就是说我们查询所有ul节点,返回是列表类型,长度为2,每个元素依然都是bs4.element.Tag
2.attrs
根据节点名查询,也可以传入一些属性来查询
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find_all(attrs={'id':'list-1'}))
print(soup.find_all(attrs={'name':'elements'}))
这里查询的时候传入的是attrs参数,参数的类型是字典类型。比如,要查询id为list-1的节点,可以传入attrs={'id':'list-1'}的查询条件,得到的结果是列表形式,包含的内容就是符合id为list-1的所有节点
对于一些常用的属性,比如id和class等,我们可以不用attrs来传递,比如要查询id为list-1的节点,可以直接传入id这个参数。还是上面的文本,我们换一种方式来查询。
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
对于class来说,由于class在python里是一个关键字,所以需要后面加一个下划线
3text
text参数可以用来匹配节点的文本,传入的形式可以是字符串,可以是正则表达式对象,
import re
html="""
<div class="panel">
<div class="panel-body">
<a>hello,this is a link</a>
<a> hello,this is a link,too</a>
</div>
</div>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find_all(text=re.compile('link')))
这里有两个a节点,其内部包含文本信息,这里在find_all()方法中传入text参数,该参数为正则表达式对象,结果返回所有匹配正则表达式的节点文本组成的列表。
find()方法
find返回的是单个元素,也就是第一个匹配的元素,而前者返回的是所有匹配的元素组成的列表
html="""
<div class="panel">
<div class="panel-heading">
<h4> hello <h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">jAY</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element"> Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
这里返回的结果不再是列表形式,而是第一个匹配的节点元素,类型依然是Tag类型
Scrapy
1、创建一个Scrapy项目
2、创建一个spider来抓取站点和处理数据
3、通过命令行将抓取的内容到处
4、将抓取内容保存到MongoDB数据库
1、需要安装scrapy框架,MongoDB和PyMongo库,
3.创建项目
创建一个scrapy项目,项目文件可以直接用scrapy命令生成,命令如下所示
scrapy startproject tutorial
这个命令可以在任意文件夹运行。如果提示权限问题,可以加sudo运行该命令。这个命令会创建一个名为tutorial的文件夹
文件夹结构如下
scrapy.py #scrapy 部署时的配置文件
tutorial#项目的某块,需要在这里引入
init.py
items.py Items的定义,定义爬虫的数据结构
middlewares.py Middlewares 的定义,定义爬取时的中间件
pipelines.py#Pipelines的定义,定义数据管道
settings.py#配置文件
spiders #放置Spiders的文件夹
init.py
4.创建Spider
Spider是自己定义的类。Scrapy用它来从网页里抓取内容,并解析抓取的结果,不过这个类必须继承Scrapy提供的Spider类scrapy.Spider,还要定义Spider的名称和起始请求,以及怎样处理爬虫后的结果的方法。
也可以使用命令行创建一个Spider。比如要生成Quotes这个Spider,可以执行如下命令:
cd tutorial
scrapy genspider quotes quotes.toscrape.com
进入刚才创建的tutorial文件夹,然后执行genspider命令。第一个参数是Spider的名称,第二个参数是网站域名,执行完毕后,spiders文件夹多了一个quotes.py 它就是刚刚创建的Spider,内容如下:
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
pass
name每个项目唯一的名字,用来区分不同的Spider
allowed_domains,它是允许爬去的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉
start_urls,它包含了Spider在启动时爬取的url列表,初始请求是由它来定义的
parse:它是Spider的一个方法,默认情况下,被调用时start_urls里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一参数传递给这个函数。该方法负责解析返回的相应、提取数据或者进一步生成要处理的请求。
5 创建Item
Item 是保存爬取数据的容器,它的使用方法和字典类似,不过,相比字典,Item多了额外的保护机制,可以避免拼写错误或者定义字段错误
创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段。观察目标网站,我们可以获取到的内容有text、author、tags。
定义Item,此时将items.py修改如下:
class QuoteItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
text=scrapy.Field()
author=scrapy.Field()
tags=scrapy.Field
这里定义了三个字段,将类的名称修改为QuoteItem,接下来爬取时我们会使用到这个Item
6 解析Response
前面我们看到,parse()方法的参数response是start_urls 里面的链接爬取后的结果。所以在parse()方法中,我们可以直接对response变量包含的内容进行解析,比如浏览器请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的url链接而得到下一个请求。
网页结构,每一页都有多个class为quote的区块,每个区块内都包含text,author,tags。那么我们找出所有的quote,然后提取每一个quote中的内容。
提取的放肆可以是css选择器或者XPath选择器,在这里我们使用CSS选择器进行选择 parse()改写如下
def parse(self, response):
quotes=response.css('.quote')
for quote in quotes:
text=quote.css('.text::text').extract_first()
author=quote.css('.author::text').extract_first()
tags=quote.css('.tags.tag::text').extract()
这里首先利用选择器选择所有的quote,并将其赋值为quotes变量,然后利用for循环对每个quote遍历,解析每个quote的内容
对text来说,观察到它的class为text,所以可以用.text选择器来选取,这个结果实际上是整个带有标签的节点,要获取它的正文内容,可以加::text来获取。结果是长度为一的列表,所以还需要用etract_first()方法来获取第一个元素。而对于tags来说,由于我们要获取所有的标签,所以用extract()方法获取整个列表即可。
7使用ITem
上文定义了Item,接下来就要使用它了,Item可以理解为一个字典,不过在声明的时候需要实例化,然后依次用刚才解析的结果赋值Item的每一个字段,最后将Item返回即可。QuotesSpider的改写如下所示:
import scrapy
from tutorial.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes=response.css('.quote')
for quote in quotes:
item=QuoteItem()
item['text']=quote.css('.text::text').extract_first()
item['author']=quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags.tag::text').extract()
yield item
如此一来,首页的所有内容被解析出来,并被赋值给一个个QuoteItem
8后续Request
上面的操作实现了从初始页面抓取内容。那么,下一页的内容该如何抓取?这就需要我们从当前页面中找到信息来生成下一个请求,然后在下一个请求的页面里找到信息再构造一个请求。这样循环往复迭代,从而实现整站的爬取。
将刚才的页面拉到最底部。
这里有一个next按钮。查看源代码,发现其链接,通过这个链接我们就可以构造下一个请求。
构造请求时需要用到scrapy.Request。这里我们传递两个参数----url和callback,这两个参数的说明一下。
url:它是请求链接
callback:它是回调函数,当指定了该回调函数的请求完成之后,获取到响应,引擎会将该响应作为参数传递给这个回调函数。回调函数进行解析或生成下一个请求,回调函数如上文的parse()所示。
由于parse()就是解析text、author、tags的方法,而下一页的结构和刚才已经解析的页面结构是一样的,所以我们可以再次使用parse()方法来做页面解析
接下来我们要做的就是利用选择器得到下一页链接并生成请求,在parse()方法后追加如下的代码:
next=response.css('.pager .next a::attr("href")').extract_first()
url=response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
第一句代码首先通过CSS选择器获取下一个页面的链接,即要获取a超链接中的href属性。这里用到了::attr(href)操作。然后再调用extract_first()方法获取内容。
第二代码调用了urljoin()方法,urljoin()方法可以将相对URL构造一个绝对的URL。例如获取到的下一页地址是/page/2 ,urljoin()方法处理后得到的结果就是完整的域名
第三局代码通过url和callback变量构造了一个新的请求,回调函数callback依然使用parse()方法。这个请求完成后,响应会重新经过parse()方法处理,得到第二页的解析结构,然后生成第二页的下一页,也就是第三页的请求。这样爬虫就进入了一个循环,直到最后一页。
import scrapy
from tutorial.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
quotes=response.css('.quote')
for quote in quotes:
item=QuoteItem()
item['text']=quote.css('.text::text').extract_first()
item['author']=quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags.tag::text').extract()
yield item
next=response.css('.pager .next a::attr("href")').extract_first()
url=response.urljoin(next)
yield scrapy.Request(url=url,callback=self.parse)
9运行
接下来,进入目录,运行如下命令
scrapy crawl quotes
就可以看到Scrapy的运行结果了
首先输出Scrapy当前的版本号以及正在启动的项目名称。接着输出了当前settings.py中一些重写后的配置。然后输出了当前所用的Middlewares和Pipelines。Middlewares默认是启用的可以在settings.py中修改。Pipelines默认是空,同样也可以在settings.py中配置。
接下来就是输出各个页面的抓取结果,可以看到爬虫一边解析,一边翻页,直到将所有内容抓取完毕。
最后Scrapy输出了整个抓取过程的统计信息,如请求的字节数、请求次数、响应次数、完成原因等。