本博客用于个人复习使用。有不对的地方希望看到的各位请不吝指出。
没必要直接看这第一步。。个人也是有点蒙 正在努力。。。第二步开始看就好。
英文解释的各个步骤就不贴上去了,各位想看直接搜就可以了。中文模板
https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/architecture.html
组件
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。 详细内容查看下面的数据流(Data Flow)部分。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。 更多内容请看 Spiders 。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。 更多内容查看 Item Pipeline 。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 下载器中间件(Downloader Middleware) 。
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 Spider中间件(Middleware) 。
数据流(Data flow)
Scrapy中的数据流由执行引擎控制,其过程如下:
1。引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
2。引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
3。引擎向调度器请求下一个要爬取的URL。
4。调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
5。一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
6。引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
7。Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
8。引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
9。(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
爬取内容简单一点 电影名 排名 导演 简介 四个信息
https://movie.douban.com/top250
比如 DouBanMovie 这个无所谓了
scrapy startproject DouBanMovie
然后进入该目录创建一个爬虫文件。
cd DouBanMovie
scrapy genspider douban movie.douban.com/top250
项目已经创建 完成
目录结构
.
├── DoubanMovie – 项目根目录
│ ├── init.py
│ ├── pycache --python运行临时文件 pyc
│ │ ├── init.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── items.py – 用来定义爬取哪些内容 (类似Django中的models)
│ ├── middlewares.py --中间件
│ ├── pipelines.py --管道,用来处理爬取的数据
│ ├── settings.py --配置文件
│ └── spiders --自定义爬虫包
│ ├── init.py
│ ├── pycache
│ │ └── init.cpython-36.pyc
│ └── douban.py --一个爬虫文件 一般来说我们在这里写的多
└── scrapy.cfg – 部署时候用的配置文件
ROBOTSTXT_OBEY = True改为
ROBOTSTXT_OBEY = False # 不遵守网站的ROBOTSTXT文件规则
将这一行注释去掉。主要是 防止网页访问过快,被封。。。
DOWNLOAD_DELAY = 3
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['http://movie.douban.com/top250']
def start_requests(self):
for url in self.start_urls:
request = scrapy.Request(url)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
def parse(self, response):
next_page = response.xpath("//span[@class='next']/a/@href")[0]
print(next_page)
if next_page:
print(response.urljoin(next_page.get()))
request = response.follow(next_page)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
注:
1 start_requests
方法如果没有重写。效果与此差不多。重写该方法是为了添加Headers中的User_Agent信息。不然下载豆瓣相关网页时,会被当成爬虫程序封掉。
2 yield 如果yield的是一个网络请求,表示继续放入网络请求队列,等待获取。可以用以下两个方法获取scrapy.Request对象。
scrapy.Request(url) # 该方法中url必须是完整的网址
response.follow(url) # 该方法,中url不仅可以是绝对地址,还可以是相对地址甚至一个链接对象。会自动拼接出一个完成的地址
response.urljoin(url) # 会根据response响应的原地址与url进行拼接得出完整地址,比较智能
通过该方法可以获取所有页,详情信息等一下获取。
scrapy crawl douban --nolog
douban是我们的爬虫名。我们可以再spider目录下创建多个爬虫文件,每个文件类属性name 就代表了我们的爬虫名。
2步骤也有使用通过response解析出下一页的链接。 parse 函数接受的response参数就是爬虫获取的相应结果,它进行了一定的封装。 from scrapy.http.response.html import HtmlResponse
就是该类。
当我们使用response进行解析时, 一般使用xpath() 获取元素。xpath语法在这里基本适用。 比如获取下一页链接它的返回值是一个SelectorList 类似于列表,里面存放的都是Selector。Selector可以看成是我们希望获取的单个元素。
from scrapy.selector import Selector
from scrapy.selector import SelectorList
next_page = response.xpath("//span[@class='next']/a/@href")[0]
Selector的get()方法相当于extract()方法。用于获取Selector的data。
比如next_page.get() 返回值就是 ?start=225&filter= 是一个类似于字符串的东西。
SelectorList的get()方法相当于extract_first()方法。用于获取列表中第一个Selector的data
所以
response.xpath("//span[@class='next']/a/@href").get()
response.xpath("//span[@class='next']/a/@href")[0].get()
返回值 一致
Selector 的getall()方法。。。这个方法比较搞笑 就是返回一个列表,列表中有一个元素,就是 self.get() 的返回值。。 注意 是列表,不是SelectorList
def getall(self):
"""
Serialize and return the matched node in a 1-element list of unicode strings.
"""
return [self.get()]
SelectorList的的getall()方法相当于extract()方法。迭代其中每个元素调用get()方法,返回一个列表,列表中是每个元素的data值。是列表
def getall(self):
"""
Call the ``.get()`` method for each element is this list and return
their results flattened, as a list of unicode strings.
"""
return [x.get() for x in self]
extract = getall
response.xpath() 方法 返回的是一个SelectorList,SelectorList中是一个个Selector对象。可以使用for selector in selectorList 方法取出每一个selector进行操作
大致明白了这些,可以继续完善代码。关于怎么解析网页。这个大家应该都会。
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['http://movie.douban.com/top250']
def start_requests(self):
for url in self.start_urls:
request = scrapy.Request(url)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
def parse(self, response):
next_page = response.xpath("//span[@class='next']/a/@href")
print(next_page)
if next_page:
request = response.follow(next_page[0])
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
# 这里是为了简单起见, 不爬取过多网页,每页只选取第一个电影进入详情页爬取。
detailDivSelector = response.xpath("//div[@class='item']")[0] # SelectorList
movieRank = detailDivSelector.xpath("./div[1]/em/text()").get()
print(movieRank)
# 获取电影详情页链接。
detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
request = response.follow(detailUrlSelector, callback=self.parse_detail)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
# 提取出详情页的div标签列表
# detailDivSelectorList = response.xpath("//div[@class='item']") # SelectorList
# for detailDivSelector in detailDivSelectorList:
# # 获取电影豆瓣中排名
# movieRank = detailDivSelector.xpath("./div[1]/em/text()").get()
# print(movieRank)
# # 获取电影详情页链接。
# detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
# request = response.follow(detailUrlSelector, callback=self.parse_detail)
# request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
# yield request
def parse_detail(self, response):
movieName = response.xpath("string(//h1/span[1])").get().strip()
movieDirector = response.xpath("//div[@id='info']/span/span[2]/a/text()").get()
movieIntroduction = response.xpath("//span[@class='short']/span/text()").get().strip()
print(movieName)
pass
可以注意到。parse 函数中,不仅解析出下一页的链接,还解析出了详情页的链接。然后使用yield 不断将这些请求加入到任务调度器。两者不同的是关于下一页的请求中, 没有附带callback参数的值,而关于详情页的请求中,我们将callback的值等于另一个解析函数名parse_detail
callback默认值为None,当callback为None时,该请求获取到响应后会默认交由parse函数处理。 而在parse中,我们正是在解析每一页,因此不用指定callback,而解析详情页则与解析主页不同,需要设计一个新的解析函数。命名为parse_detail,当希望将某一请求的响应结果交由该函数处理时,就要指定callback=parse_detail,由此详情页请求的响应结果会交由parse_detail()函数处理。
items.py 文件中定义我们爬取需要获取的数据。比如我们需要获取 电影排名,电影名称,电影导演,电影简介。在items.py 文件中需要定义。
class DoubanmovieItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
movieRank = scrapy.Field()
movieName = scrapy.Field()
movieDirector = scrapy.Field()
movieIntroduction = scrapy.Field()
没有具体的类型要求。然后,可以在我们的爬虫文件douban.py中引入该类,类似于字典(也可以转换成字典类型)。修改爬虫文件。
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['http://movie.douban.com/top250']
def start_requests(self):
for url in self.start_urls:
request = scrapy.Request(url)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
def parse(self, response):
# 不断获取下一页 直到获取失败
next_page = response.xpath("//span[@class='next']/a/@href")
print(next_page)
if next_page:
request = response.follow(next_page[0])
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
movieItem = DoubanmovieItem() # 创建item对象
detailDivSelector = response.xpath("//div[@class='item']")[0] # SelectorList
movieRank = detailDivSelector.xpath("./div[1]/em/text()").get()
movieItem['movieRank'] = movieRank
# 获取电影详情页链接。
detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
request = response.follow(detailUrlSelector, callback=self.parse_detail, meta={'item': movieItem})
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
# 提取出详情页的div标签列表
# detailDivSelectorList = response.xpath("//div[@class='item']") # SelectorList
# for detailDivSelector in detailDivSelectorList:
# # 获取电影豆瓣中排名
# movieRank = detailDivSelector.xpath("./div[1]/em/text()").get()
# print(movieRank)
# # 获取电影详情页链接。
# detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
# request = response.follow(detailUrlSelector, callback=self.parse_detail)
# request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
# yield request
def parse_detail(self, response):
movieItem = response.meta["item"]
movieName = response.xpath("string(//h1/span[1])").get().strip()
movieDirector = response.xpath("//div[@id='info']/span/span[2]/a/text()").get()
movieIntroduction = response.xpath("string(//div[@class='indent']/span)").get().strip()
movieItem["movieName"] = movieName
movieItem["movieDirector"] = movieDirector
movieItem["movieIntroduction"] = movieIntroduction
print(movieItem["movieName"], movieItem["movieRank"])
yield movieItem
movieItem = DoubanmovieItem()
然后可以对定义好的属性以类似字典的形式赋值,取值。
moviItem[‘属性名’] = ‘属性值’
而如果希望传递参数,一般将要传递的对象放入一个字典,然后通过request请求中的meta参数传递,meta的值是一个字典类型对象。
movieItem['movieRank'] = movieRank
# 获取电影详情页链接。 这一句和传递参数无关。。。
detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
request = response.follow(detailUrlSelector, callback=self.parse_detail, meta={'item': movieItem})
而在进行解析时,response的meta属性就是之前传递的字典类型对象。
movieItem = response.meta["item"]
如此,就完成了参数的传递,解析完成后。在parse_detail函数中yield 一个item。这个scrapy.Item类型的对象,明显是与请求不同的,它不会再加入调度队列,而是经过管道 pipelines.py 如果希望在管道中处理这些数据。。。那么在setting.py文件中将 这三行代码注释去掉
#ITEM_PIPELINES = {
# 'DouBanMovie.pipelines.DoubanmoviePipeline': 300,
#}
这样每一个yield 的item都会经过pipelines.py文件中 DoubanmoviePipeline的处理。process_item 就是对item的处理,参数item就是yield的 DoubanmovieItem对象,spider就是爬虫对象
class DoubanmoviePipeline(object):
def process_item(self, item, spider):
return item
如果希望对数据进行清洗,或者保存到数据库。可以在这里进行。需注意的是open_spider会在爬虫开启时执行一次,而close_spider会在爬虫关闭时执行一次。而proess_item则会在每个item对象到达时触发执行。
class MoviesinfoPipeline(object):
# 爬虫开启时执行 只会执行一次
def open_spider(self, spider):
print(spider, "打开爬虫, 经过管道1")
# 开启数据库
print("连接成功")
def process_item(self, item, spider):
print(item["movieName"])
# 数据库保存语句
return item
def close_spider(self, spider):
# 爬虫关闭时执行 只会执行一次
print("关闭爬虫,经过管道1", spider)
# 关闭数据库连接
如果希望有多个管道控制,可以仿照该格式创建一个新的类,实现process_item方法。open_spider和close_spider并不是必须的。。。比如新建一个类 MoviesinfoPipeline2
class DoubanmoviePipeline2(object):
def open_spider(self, spider):
print(spider, "管道2开启")
def process_item(self, item, spider):
print(item['movieName'])
return item
def close_spider(self, spider):
print(spider, "管道2关闭")
然后将该类也加入到setting中的管道里即可, 300, 301可以看成经过管道的顺序,也就是说item会先经过300这个管道,再经过301这一个。。。
ITEM_PIPELINES = {
'DouBanMovie.pipelines.DoubanmoviePipeline': 300,
'DouBanMovie.pipelines.DoubanmoviePipeline2': 301,
}
可以看到一个item的movieaName输出了三次,因为我们在yield item之前输出了一次。管道1中输出一次,管道2中输出一次。。。
class DoubanSpider(scrapy.Spider):
name = 'douban'
allowed_domains = ['movie.douban.com']
start_urls = ['http://movie.douban.com/top250']
def start_requests(self):
for url in self.start_urls:
request = scrapy.Request(url)
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
def parse(self, response):
# 不断获取下一页 直到获取失败
next_page = response.xpath("//span[@class='next']/a/@href")
print(next_page)
if next_page:
request = response.follow(next_page[0])
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
# 提取出详情页的div标签列表
detailDivSelectorList = response.xpath("//div[@class='item']") # SelectorList
for detailDivSelector in detailDivSelectorList:
movieItem = DoubanmovieItem()
# 获取电影豆瓣中排名
movieRank = detailDivSelector.xpath("./div[1]/em/text()").get()
movieItem["movieRank"] = int(movieRank)
# 获取电影详情页链接。
detailUrlSelector = detailDivSelector.xpath("./div[2]/div/a/@href")[0]
request = response.follow(detailUrlSelector, callback=self.parse_detail, meta={'item': movieItem})
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
yield request
def parse_detail(self, response):
movieItem = response.meta["item"]
movieName = response.xpath("string(//h1/span[1])").get().strip()
movieDirector = response.xpath("//div[@id='info']/span/span[2]/a/text()").get()
movieIntroduction = response.xpath("string(//div[@class='indent']/span)").get().strip()
movieItem["movieName"] = movieName
movieItem["movieDirector"] = movieDirector
movieItem["movieIntroduction"] = movieIntroduction
yield movieItem
管道中代码也改一下
class DoubanmoviePipeline(object):
def open_spider(self, spider):
print(spider, "管道1开启")
def process_item(self, item, spider):
print(item['movieRank'])
return item
def close_spider(self, spider):
print(spider, "管道1关闭")
class DoubanmoviePipeline2(object):
def open_spider(self, spider):
print(spider, "管道2开启")
def process_item(self, item, spider):
print(item['movieName'])
return item
def close_spider(self, spider):
print(spider, "管道2关闭")
关于中间件,在进行相关模块之前会经过中间件。可以进行的操作有去重,取消下载,附加请求信息等。 来试一一个最简单的。。。附加信息。
第1步到第5步,我们要为每一个请求加上头部信息,否则豆瓣就不会响应。而我们一共写了三次加headers的操作。就可以利用中间件,在每个请求准备下载时加上User-Agent信息。只用写一次即可。
找到 middlewares.py 中DoubanmovieDownloaderMiddleware类。修改方法
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
每个请求准备下载时都会进过这个下载中间件。直接加上一行代码。
request.headers["User-Agent"] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2843.400'
然后要在setting文件中打开这个中间件,去掉注释即可
DOWNLOADER_MIDDLEWARES = {
'DouBanMovie.middlewares.DoubanmovieDownloaderMiddleware': 543,
}
这样每一个请求在 请求下载时headers都会加上User-Agent信息。这样爬虫文件中就可以省掉该步骤。。。
类似于下列代码之类。ip表示ip地址,port表示端口号。。。有些代理是https的那么http换成http即可。不管是在spider中修改还是中间件中修改,效果是一样的。
request.meta['proxy'] = 'http://ip:port'
scrapy 的cookie信息比较特殊。如果直接在请求的headers中添加似乎并不可行(这个待验证,不确定)。需要将cookies信息转成字典格式然后在构造request请求时传递给cookies参数。。比如下面这种。
cookies = 'bid=8s1s39hxPA0; __gads=ID=66eccd2e0389967d:T=1580894607:S=ALNI_MYzn11KuDYChRvoQUIerqWhHNFnYA; push_noty_num=0; push_doumail_num=0; __utmc=30149280; __utmv=30149280.21041; ap_v=0,6.0; dbcl2="210411812:ajHV7kXtARE"; ck=yDli; __utma=30149280.1291680489.1580894608.1580898784.1580902568.3; __utmz=30149280.1580902568.3.2.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/passport/login; __utmt_douban=1; douban-profile-remind=1; __utmt=1; __utmb=30149280.13.10.1580902568'
cookies = {i.split("=")[0]: i.split("=")[1] for i in cookies.split(";")}
request = scrapy.Request(url, cookies=cookies)
在下载中间件中为request.cookis赋值也是可以的。
cookies = 'bid=8s1s39hxPA0; __gads=ID=66eccd2e0389967d:T=1580894607:S=ALNI_MYzn11KuDYChRvoQUIerqWhHNFnYA; push_noty_num=0; push_doumail_num=0; __utmc=30149280; __utmv=30149280.21041; ap_v=0,6.0; dbcl2="210411812:ajHV7kXtARE"; ck=yDli; __utma=30149280.1291680489.1580894608.1580898784.1580902568.3; __utmz=30149280.1580902568.3.2.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/passport/login; __utmt_douban=1; douban-profile-remind=1; __utmt=1; __utmb=30149280.13.10.1580902568'
cookies = {i.split("=")[0]: i.split("=")[1] for i in cookies.split(";")}
request.cookies = cookies
# Disable cookies (enabled by default)
# COOKIES_ENABLED = False
个人感觉下面两行代码是等价的。。。有些博客说注释状态,非注释=True,非注释=False的效果都是不同的。这个可能是我理解的不够深吧。。。
# COOKIES_ENABLED = False
COOKIES_ENABLED = True
而当该句话是以下情况的时候,如果想要以登录状态获取网页信息。
COOKIES_ENABLED = False
那就需要在setting中找到这一句
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
将cookies信息加上去,并去掉注释。
注:如果COOKIES_ENABLED = False, 那么cookies的设置只能在settings中完成。
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'Cookie': 'bid=8s1s39hxPA0; __gads=ID=66eccd2e0389967d:T=1580894607:S=ALNI_MYzn11KuDYChRvoQUIerqWhHNFnYA; push_noty_num=0; push_doumail_num=0; __utmc=30149280; __utmv=30149280.21041; ap_v=0,6.0; dbcl2="210411812:ajHV7kXtARE"; ck=yDli; __utma=30149280.1291680489.1580894608.1580898784.1580902568.3; __utmz=30149280.1580902568.3.2.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/passport/login; __utmt_douban=1; douban-profile-remind=1; __utmt=1; __utmb=30149280.13.10.1580902568',
}
当我们准备获取的网页十分规律时,我们可以继承CrawlSpider类实现。如
https://www.dushu.com/news/
横向只需不断查询下一页,纵向就是把每一页的详情页提取出来。
如果使用Spider。
class DushunnewsSpider(scrapy.Spider):
name = 'dushunews'
allowed_domains = ['www.dushu.com']
start_urls = ['http://www.dushu.com/news/']
def parse(self, response):
# 提取下一页链接 callback为None 继续在该parse中解析
# 相当于CrawlSpider中的 横向解析 广度
next_page = response.xpath("//a[contains(text(), '下一页')]/@href")
print(next_page)
if next_page: # 如果存在下一页
yield scrapy.Request(response.urljoin(next_page.get()))
# 提取详情页 callback 指向解析详情页
# 相当于CrawlSpider 中的纵向解析 深度
news_detail_page_list = response.xpath("//div[contains(@class, 'news-item')]/h3/a")
for news_detail_page in news_detail_page_list:
yield response.follow(news_detail_page, callback=self.parse_detail)
def parse_detail(self, response):
title = response.xpath("//h1/text()").get()
introduction = response.xpath("//blockquote/p/text()").get()
print(title)
换成CrawlSpider
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
# 对于一些有规律的 网页 可以继承CrawlSpider
# 指定rules提取网页信息更加方便。
class DushuRuleSpider(CrawlSpider):
name = 'dushu_rule'
allowed_domains = ['www.dushu.com']
start_urls = ['http://www.dushu.com/news/']
# 网页提取规则
rules = (
# 横向爬虫规则 对每一页的 url 生成一个请求
# 根据该url得到response, 由于follow=True, 会按照规则继续从response中解析出下一个url
Rule(link_extractor=LinkExtractor(restrict_xpaths="//a[contains(text(), '下一页')]"), follow=True),
# 纵向爬虫规则 对每一个items 分别生成请求
#请求生成的Response 交由指定的callback解析, 同时 follow=False不会再使用该规则对新得到的Response进行解析
Rule(link_extractor=LinkExtractor(restrict_xpaths="//div[contains(@class, 'news-item')]/h3/a"),
callback='parse_detail', follow=False)
)
def parse_detail(self, response):
title = response.xpath("//h1/text()").get()
introduction = response.xpath("//blockquote/p/text()").get()
print(title)
不能写parse函数。rules元组中,两个规则分别负责广度上爬取和深度上爬取。
明确一点。该CrawlSpider比较适合规律性网站。