Python的爬虫框架Scrapy基本使用

本博客用于个人复习使用。有不对的地方希望看到的各位请不吝指出。

一。 几乎每个关于Scrapy的框架介绍都会来这么一个图 我也跟个风。。。

没必要直接看这第一步。。个人也是有点蒙 正在努力。。。第二步开始看就好。
Scrapy架构图
英文解释的各个步骤就不贴上去了,各位想看直接搜就可以了。中文模板
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,引擎关闭该网站。

二、这里正式开始。创建一个项目

我们尝试爬取豆瓣电影排行榜250

爬取内容简单一点 电影名 排名 导演 简介 四个信息
https://movie.douban.com/top250

准备好开发环境后命令行执行。

scrapy startproject 项目名

比如 DouBanMovie 这个无所谓了

scrapy startproject DouBanMovie

然后进入该目录创建一个爬虫文件。

scrapy genspider 爬虫名 爬取网址
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 – 部署时候用的配置文件

三、完成爬虫文件。

1。修改settings.py中文件

ROBOTSTXT_OBEY = True改为
ROBOTSTXT_OBEY = False # 不遵守网站的ROBOTSTXT文件规则
将这一行注释去掉。主要是 防止网页访问过快,被封。。。
DOWNLOAD_DELAY = 3

2。在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")[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不仅可以是绝对地址,还可以是相对地址甚至一个链接对象。会自动拼接出一个完成的地址
  • 3 还有一个方法用于拼接出完成地址
response.urljoin(url)	# 会根据response响应的原地址与url进行拼接得出完整地址,比较智能

通过该方法可以获取所有页,详情信息等一下获取。

scrapy crawl douban --nolog

douban是我们的爬虫名。我们可以再spider目录下创建多个爬虫文件,每个文件类属性name 就代表了我们的爬虫名。
获取所有页

3。 解析爬虫爬取的response文件。

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()函数处理。

4。yield request与yield item 的不同。与不同解析函数之间参数传递。

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中输出一次。。。

5。将spider中文件修改一下,爬取每一个详情页,而不是每个页面中第一个详情页。

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关闭")

输出结果

6。中间件,下载中间件和爬虫中间件。

关于中间件,在进行相关模块之前会经过中间件。可以进行的操作有去重,取消下载,附加请求信息等。 来试一一个最简单的。。。附加信息。
第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代理等方式解决,或者请求时附带cookie信息保持登录状态。

1,使用ip代理。scrapy中使用代理十分简单。

类似于下列代码之类。ip表示ip地址,port表示端口号。。。有些代理是https的那么http换成http即可。不管是在spider中修改还是中间件中修改,效果是一样的。

request.meta['proxy'] = 'http://ip:port'
2,附加cookie。保持登录状态,豆瓣,微博网页之类保持登录都是在客户端存放一个cookie信息,而服务器端也存放着cookie-id,以此来验证用户登录状态。因此当我们发出请求时,附带cookie信息时,是可以访问需登录才可查看的页面的。

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

这里需注意的是setting设置。。。

# Disable cookies (enabled by default)
# COOKIES_ENABLED = False

disable 使。。。失去作用,就是使cookies失效,个人理解是如果将# COOKIES_ENABLED = False代码注释去掉,那么即使在spider或者中间件设置了cookies,这个cookies也是无效的。。。而当这句话在注释状态时,默认cookies是有效的,也就是说在spider或中间件中设置的cookies可以帮助我们通过登录状态的校验。

个人感觉下面两行代码是等价的。。。有些博客说注释状态,非注释=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其实这个我想单独写一篇博客用来巩固的。但是又觉得理解不深,篇幅太短,没必要。所以就放在这里了。

当我们准备获取的网页十分规律时,我们可以继承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比较适合规律性网站。

发布了59 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/dandanfengyun/article/details/104186422