scrapy学习(完全版)

scrapy1.6中文文档

Scrapy框架

  • 下载页面
  • 解析页面
  • 并发
  • 深度

安装

  • scrapy学习教程
  • 如果安装了anconda,可以在anaconda prompt中使用conda install scrapy
  • 也可以使用pycharm安装

使用

  1. 指定初始URL
  2. 解析响应内容
    1. 给调度器
    2. 给item;pipeline用于做格式化;持久化

创建一个初始scrapy初始项目初始工作

① 、在windows终端(cmd)输入,进行前期工作

  1. scrapy startproject 工程名 >(注意你此时的路径就是你工程所在的路径)
  2. cd 工程名 (转到你工程所在的文件夹)
  3. scrapy genspider 爬虫名 要爬的网址 (创建一个爬虫)
  4. 打开工程文件中的 settings.py 文件 ,进行基本设置
  • 令 ROBOTSTXT_OBEY = False (不遵守robot协议,因为如果遵守基本上爬取不到东西)
  • 在Override the default request headers: 中 启动headers 加'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 QIHU 360SE'

②、spiders文件里的编辑

  1. 打开 爬虫名.py 进行编辑 (编写你的爬虫文件)
  2. scrapy crawl 爬虫名 (运行爬虫,此时应处于你的工程路径下,如果后面加 "--nolog会只输出结果)
  3. 在spider文件的初始处理函数中写入:form scrapy.http import Request , yield Request(url = url,callback = self.parse) 可以将新要访问的url添加到调度器中,然后调度器进行访问与下载,得到的response交给parse函数处理 (如果要执行深度的话,必须使callback =self.parse 这样才能迭代
  4. response的方法与属性:
  • response.text 返回服务器返回的response(一般为网页源代码)的字符串形式
  • response.body 返回服务器返回的response(一般为网页源代码)的字节形式
  • response.xpath('str') 以xpath语法定位元素,返回为selector或者selector_lsit对象
  • response.meta 返回的是一个字典,其中包含了很多信息,例如 depth
  • selector或者seletor_list对象可以使用xpath继续定位元素
  • selector_list对象可以迭代
  • 通过对selector或者selector_list对象使用extract()等价于getall() 或者 extract_first()等价于get()方法,可以将selector或seletor_list对象中的文本信息提取出来,那个getall()和extract()是生成字符串组成的列表
  1. 可以通过重写该方法,让访问start_urls中的url的访问返回的response对象调用新的方法(即指定最开始处理请求的方法),即可以将self.parse改成别的
    def start_requests(self):
        for url in self.start_urls:
            yield Request(url,callback=self.parse)

③、items文件中的编辑

  1. 通过在items.py文件的XXXXItem类中写入:例如:author =scrapy.Field() ,content =scrapy.Field() 可以将要传给pipelines的数据进行格式化,即用一个XXXItem对象进行封装。然后通过在spider文件初始函数中中写入:from 工程名.items import XXXItem ,item = XXXXItem(author = author,content =content)就可以将数据进行格式化,将author,content封装到item对象中,再通过写yield item 自动将item对象递交给引擎,然后引擎再传给pipelines

④ 、pipelines文件中的编辑

  1. settings文件中 开启Configure item pipelines 中的那个字典,其中的数字代表了pipelines执行的优先级,你可以在 pipelines文件中定义多个类,通过优先级决定了他们执行的顺序,小的优先。
  2. 在pipelines中传递的item就是从spider中那边传过来的数据,可以通过dict(item)将其变成字典进行储存,也可以用scrapy中内置的类进行存储,见后面;spider指的是spider对象,他有name属性 ,因而可以通过 if spider.name = "XXX"判断是那个蜘蛛爬取的信息,进而有区别的进行处理。
  3. pipeline对象中process_item()方法是当spiders有yield语句 或reurn 数据语句中会执行该方法,open_spider()方法是当爬虫打开后会调用这个函数,close_spider()方法是爬虫完成后会调用该方法。
    因而在打开文件的操作可以放在,open_spider()方法中,关闭文件的操作可以放在close_spider()方法中,这样可以防止重复的打开与关闭文件。from_crawler()方法用于创建对象,并获取配置文件中的相关数据。
import json
#from scrapy.exporters import  JsonItemExporter
from scrapy.exceptions import DropItem
class QsbkPipeline(object):
    # def __init__(self):
    #     self.fp = open('duanzi.json','w',encoding = 'utf-8')
    #
    def __init__(self):
        '''
        进行数据初始化
        '''
        # self.fp = open('duanzi.json','wb',encoding = 'utf-8')  # 这里要用二进制方式打开,因为JsonItemExporter写入文件的形式是二进制形式
        # self.exporter = JsonItemExporter(self.fp,ensure_ascii = False,encoding = 'utf-8')
        # self.exporter.start_exporting()
        pass

    @classmethod
    def from_crawler(cls,crawler):
        '''
        创建对象,并获取配置文件(settings)中的相关数据
        :param crawler:
        :return:
        '''
        # conn_str = crawler.settings.get("DB") # 获取配置文件(settings文件)中的DB值,
        # return cls(conn_str)# 创建对象,并向对对象中传入配置文件中的DB值,可以在__inint__方法中,获取到



    def process_item(self, item, spider):
        '''
        当spiders有yield语句 或reurn 数据语句中会执行该方法
        :param item:
        :param spider:
        :return:
        '''
        # item_json = json.dumps(dict(item),ensure_ascii = False)
        # self.fp.write(item_json+'
')
        # return item
        print(item['author']+":"*5+item['content'])
        # return item  # 当有多个pipeline对象时,这里返回一个item,即可以将item传递给下一个pipeline对象
        # raise DropItem() # 若不想将item交给下一个pipeline对象,应该使用这种方式,因为后面有某种方式可以监听


    def open_spider(self,spider):
        '''
        爬虫打开后会调用这个函数
        :param spider:
        :return:
        '''
        pass

    def close_spider(self,spider):
        '''
        爬虫完成后会调用这个函数
        :param spider:
        :return:
        '''
        pass

scrapy中的自带的去重方法:

输入这个语句即可看到,scrapy中自带类的语句,模范这个类,自定义写一个新的文件,自定义相关方法,即可自定义去重

from scrapy.dupefilters import RFPDupeFilter

自定义去重方法(scrapy中默认是可以在传入调度器中的url去重的)

  1. 也可以自定义对象,来进行自定义去重 例子:新创建一个含有类RepeatFilter文件,在settings文件中加入以下下配置信息,进行自定义去重类的注册(激活)
 DUPEFILTER_CLASS = '工程名.类所在的文件名.RepeatFilter'

类RepeatFilter文件中的代码如下:

# 这个类的名称可以随意定义
class RepeatFilter(object):
    def __init__(self):
        '''
        2、进行对象初始化
        '''
        self.visited_set = set() #定义一个集合,用来存储非重复地url

    @classmethod  # 这是一个类方法
    def from_settings(cls, settings):
        '''
        1、创建对象
        '''
        return cls() #在这里创建了一个实例,因而会去执行__init__()方法

    def request_seen(self, request):
        '''
        4、对象被访问即调用该方法
        '''
        if request.url in self.visited_set:
            return True
        self.visited_set.add(request.url)
        return False

    def open(self):  # can return deferred
        '''
        3、打开蜘蛛,开始爬取
        '''
        pass

    def close(self, reason):  # can return a deferred
        '''
        停止爬取
        '''
        pass

    def log(self, request, spider):  # log that a request has been filtered
        pass

cookies问题(模拟登陆时用到的)

# -*- coding: utf-8 -*-

# Scrapy settings for qsbk project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://doc.scrapy.org/en/latest/topics/settings.html
#     https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://doc.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'qsbk' # 爬虫的名字,会放在后面的USER_AGENT中

# 爬虫的路径
SPIDER_MODULES = ['qsbk.spiders']
NEWSPIDER_MODULE = 'qsbk.spiders'

# 标识请求的身份
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'qsbk (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36'

# Obey robots.txt rules  是否遵守爬虫协议
ROBOTSTXT_OBEY = False

# 并发请求的最大数目,一次可以发出的最大请求数目
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# 当对同一网站发送不同请求之间的延迟(单位:秒)
DOWNLOAD_DELAY = 2
# The download delay setting will honor only one of:
# 具体针对某个域名或IP的最大并发数,比上面的更精准
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# 是否帮你获取cookies,默认为True
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# 是否监听爬虫的状态,默认为True,在cmd中输入:telnet 127.0.0.1 6023 即可进入监听状态,输入相应操作命令即
# 可获得相关信息,例如:est()
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# 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',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36'
}

# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'qsbk.middlewares.QsbkSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'qsbk.middlewares.QsbkDownloaderMiddleware': 543,
#}

# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
# 注册扩展
EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
    'qsbk.my_extention.MyExtend':200

}
from scrapy.extensions.telnet  import TelnetConsole
# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
# 注册pipeline,即pipelines文件的代码能运行
ITEM_PIPELINES = {
   'qsbk.pipelines.QsbkPipeline': 300,  # pipelines执行的优先级
}


# 智能请求,让请求间的时间间隔变得不固定,这里填的参数是智能限速算法的相关参数
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True  # 是否允许智能请求
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5  # 初始请求的延迟时间(秒)
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60  # 请求间的最大延迟时间
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# 15. 调度器队列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler

# 做缓存
# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
# 是否启用缓存策略 默认为False
# HTTPCACHE_ENABLED = True
# 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可  默认的缓存策略
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略  这种缓存策略比上面的好,一般用这个就行,上面的可以不用
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"
# 缓存超时时间,若请求时间超过多少我就不存了
# HTTPCACHE_EXPIRATION_SECS = 0
# 缓存保存路径
# HTTPCACHE_DIR = 'httpcache'
# 缓存忽略的Http状态码,如果状态码是这个,我就不存了
# HTTPCACHE_IGNORE_HTTP_CODES = []
# 缓存存储的插件,即缓存的具体方法
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
# from scrapy.extensions.httpcache import FilesystemCacheStorage # 通过这条语句可以看到里面具体的方法


#DEPTH_LIMIT = 1 # 指定"递归"的层数 可以限制爬取深度,这个深度是与start_urls中定义url的相对值。也就是相对url的深度。
                # 例如定义url为:http://www.domz.com/game/,DEPTH_LIMIT=1那么限制爬取的只能是此url下一级的网页。深度大于设置值的将被ignore。
#DEPTH_PRIORITY =0 # 只能是0或1,依次表示是深度度优先还是广度优先,调度器中url访问的顺序,默认为0
# 进行自定义去重类的注册(激活)
# DUPEFILTER_CLASS = 'qsbk.my_dupefilter.RepeatFilter'
# 里面的配置变量名  必须大写

缓存

缓存:在更近的地方快速将数据读取出来
在settings文件中进行相关注册与配置

代理

  1. scrapy中默认的代理必须依赖环境变量 (不好用,使用自定义的更好)
from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
    
#默认的代理要添加到这里面去,格式如下
os.environ
    {
    http_proxy:http://代理账号:代理密码@192.168.11.11:9999/
    https_proxy:http://192.168.11.11:9999/
    }
  1. 使用自定义代理插件
    例子:新创建一个含有类ProxyMiddleware文件,在settings文件中加入相关配置信息,进行自定义代理的注册(激活)
    新建文件的代码如下:

    import base64
    import random
    def to_bytes(text, encoding=None, errors='strict'):
        if isinstance(text, bytes):
            return text
        if not isinstance(text, six.string_types):
            raise TypeError('to_bytes must receive a unicode, str or bytes '
                            'object, got %s' % type(text).__name__)
        if encoding is None:
            encoding = 'utf-8'
        return text.encode(encoding, errors)
    
    # 这个类的名称不能改变    
    class ProxyMiddleware(object):
        def process_request(self, request, spider):
            PROXIES = [
                {'ip_port': '111.11.228.75:80', 'user_pass': ''},
                {'ip_port': '120.198.243.22:80', 'user_pass': ''},
                {'ip_port': '111.8.60.9:8123', 'user_pass': ''},
                {'ip_port': '101.71.27.120:80', 'user_pass': ''},
                {'ip_port': '122.96.59.104:80', 'user_pass': ''},
                {'ip_port': '122.224.249.122:8088', 'user_pass': ''},
            ]
            # 把代理就填入PROXIES字典中
            proxy = random.choice(PROXIES)
            if proxy['user_pass'] is not None:
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
                encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))
                request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass)
                print("**************ProxyMiddleware have pass************" + proxy['ip_port'])
            else:
                print ("**************ProxyMiddleware no pass************" + proxy['ip_port'])
                request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])
                

在settings文件中配置以激活

    DOWNLOADER_MIDDLEWARES = {
       '工程名.代理插件文件名.ProxyMiddleware': 500,
    }

https访问(scrapy默认不支持自定义证书访问)

  1. 要爬取的网站使用的是可信任证书(默认支持)
    这是在settings中默认的配置,一般不可见
        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory"
  1. 要爬取网站使用的自定义证书
    例子:需要改写MySSLFactory类,在settings文件改变相关配置信息
    新建文件的代码如下:
from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory
from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate)

class MySSLFactory(ScrapyClientContextFactory):
    def getCertificateOptions(self):
        from OpenSSL import crypto
        # 这里只需要改变你拿到的证书的两个文件的地址即可
        v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read())
        v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read())
        return CertificateOptions(
            privateKey=v1,  # pKey对象
            certificate=v2,  # X509对象
            verify=False,
            method=getattr(self, 'method', getattr(self, '_ssl_method', None))
        )

相关在settings文件中的配置信息

        DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
        DOWNLOADER_CLIENTCONTEXTFACTORY = "工程名.文件名.MySSLFactory"

中间件

1. 自定义下载中间件

传入给调度器的Request请求会一个个经过自定义的下载中间件(DOWNLOADER_MIDDLEWARES),顺序为settings文件中的配置信息数值得大小(小的优先),要是自定义的下载中间件都不下载,最后会由scrapy内置的下载中间件去下载。

  • 一般执行方法的顺序是,先去执行每一个下载中间件的process_request方法(顺序为setting文件中的配置权顺序,小的先),然后以相反的顺序执行每一个下载中间件的process_response方法。若前面过程中出现了异常,会执行每一个下载中间件的process_exception方法。然后执行爬虫中间件中的process_spider_input方法,最后会执行爬虫中的yield Request(callback=fun1)指明的fun1方法
  • 有一个下载中间件中的process_request方法中,返回了response(即存在 return response),后续就不在执行后续中间件的process_request方法
  • process_response 方法中必须要有 return response ,否知resposne传不到其它的process_response方法和spider中的方法中
自定义下载中间件的文件中的示例代码:
class DownMiddleware1(object):
    def process_request(self, request, spider):
        '''
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request:
        :param spider:
        :return:
            None,继续后续中间件去下载;,没有return语句时的默认形式
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        '''
        pass

    def process_response(self, request, response, spider):
        '''
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return:
            Response 对象:转交给其他中间件process_response
            Request 对象:停止中间件,request会被重新调度下载
            raise IgnoreRequest 异常:调用Request.errback
        '''
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        '''
        当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
        :param response:
        :param exception:
        :param spider:
        :return:
            None:继续交给后续中间件处理异常;
            Response对象:停止后续process_exception方法
            Request对象:停止中间件,request将会被重新调用下载
        '''
        return None

setting文件中的配置:
DOWNLOADER_MIDDLEWARES = {'工程名.文件名.DownMiddleware1':543}

爬虫中间件

自定义spider中间件的文件中的示例代码:
class SpiderMiddleware(object):

    def process_spider_input(self, response, spider):
        '''
        下载中间件完成后,调用该方法,然后交给spider中的parse(指callbakc指定的函数)处理
        :param response:
        :param spider:
        :return:
        '''
        pass

    def process_spider_output(self, response, result, spider):
        # 这个result就是parse中yield Request或yield Item的生成器
        '''
        spider处理完成,返回时调用该方法
        :param response:
        :param result:
        :param spider:
        :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
        '''
        return result

    def process_spider_exception(self, response, exception, spider):
        '''
        异常调用
        :param response:
        :param exception:
        :param spider:
        :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
        '''
        return None

    def process_start_requests(self, start_requests, spider):
        '''
        爬虫启动时调用
        :param start_requests:
        :param spider:
        :return: 包含 Request 对象的可迭代对象
        '''
        return start_requests
    
setting文件中的配置:
SPIDER_MIDDLEWARES = {'工程名.文件名.SpiderMiddleware':500}

自定制命令

  • 在spiders同级创建任意文件夹,如:commands
  • 在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令) 文件中的代码如下:
from scrapy.commands import ScrapyCommand
from scrapy.utils.project import get_project_settings

    class Command(ScrapyCommand):
        requires_project = True
        def syntax(self):
            return '[options]'

        def short_desc(self):
            return 'Runs all of the spiders'

        def run(self, args, opts):
            # 找到所有的爬虫名称
            spider_list = self.crawler_process.spiders.list()
            for name in spider_list:
                self.crawler_process.crawl(name, **opts.__dict__)
            self.crawler_process.start()
  • 在settings.py 中添加配置
COMMANDS_MODULE = '工程名.文件名'
  • 在cmd中,在项目目录执行命令:scrapy crawlall,即可运行所有的爬虫

scrapy shell(命令行交互模式)

由于scrapy框架比较大,每次运行起来都要等待一段时间。因此要去验证我们写的提取规则(xpath等)是否正确,是一个比较麻烦的事情。因此scrapy提供了一个shell,用来方便的测试规则。当然也不仅仅只局限于这一个功能。

  1. 打开Scrapy Shell:打开cmd终端,进入到Scrapy项目所在的目录,然后进入到scrapy框架所在的虚拟环境中,输入命令:
scrapy shell 链接
# 例如:scrapy shell https://www.baidu.com
  1. 进入到scrapy的shell环境中后,你就可以跟在爬虫的parse方法中一样使用了。
  2. 例如输入 content = response.xpath('......') 进行元素定位与选取
  3. exit()退出该模式

快速输出

不需要在pipelines编写相关语句,但是需要在items格式化,但是要在spider 中yield item
在cmd窗口中(此时应在该项目目录中)输入:

scrapy crawl 爬虫名 -o 输出文件名.json  # 输出为json文件
scrapy crawl 爬虫名 -o 输出文件名.csv  # 输出为csv文件
scrapy crawl 爬虫名 -o 输出文件名.jl  # 输出为每一个item为一行的json文件
scrapy crawl 爬虫名 -o 输出文件名.xml  # 输出为xml文件
scrapy crawl 爬虫名 -o 输出文件名.pickle  # 输出为pickle文件

利用py文件启动爬虫(即不需要在cmd窗口中输入命令)

在项目文件夹下新建一个py文件,如图:


文件中代码示例如下:

from scrapy import cmdline
# 这个文件是执行命令行的语句,
cmdline.execute(['scrapy','crawl', 'qsbk_spider'])

这样,以后通过点击start.py文件,即可执行爬虫。

pipelines中优化存储数据的方式

方式1:
from scrapy.exporters import JsonItemExporter
#这种方式是先将item以字典形式添加到列表中,然后在finish_expoerting()时将整个列表写入js文件中。这种方式比较占内存.
 但是整个文件是一个json文件形式,可以直接load
# 还有其他导出方式,在exporters文件中去寻找相关类

class QsbkProjectPipeline(object):
    def __init__(self):
        self.f = open('duanzi.json','wb') # 这里要用二进制形式打开,因为JsonItemExporter是以二进制形式写入文件的
        self.exporter = JsonItemExporter(self.f,ensure_ascii = False,encoding ='utf-8')
        self.exporter.start_exporting()

    def process_item(self, item, spider):
        self.exporter.export_item(item)

    def open_spider(self,spider):
        print("爬虫开始了啊")

    def close_spider(self,spider):
        self.exporter.finish_exporting()
        self.f.close()
        print('爬虫结束了')
方式2:
from scrapy.exporters import JsonLinesItemExporter
# 这种方式是一个字典(item),写入json文件中的一行,整个文件并不满足json文件形式,这样在读取该文件时,应该一行行读取,然后挨个loads
# 并且这种方式不需要,start_exporting()与finish_exporting()语句,节省内存
class QsbkProjectPipeline(object):
    def __init__(self):
        self.f = open('duanzi.json','wb') # 这里要用二进制形式打开,因为JsonItemExporter是以二进制形式写入文件的
        self.exporter = JsonLinesItemExporter(self.f,ensure_ascii = False,encoding ='utf-8')

    def process_item(self, item, spider):
        self.exporter.export_item(item)

    def open_spider(self,spider):
        print("爬虫开始了啊")

    def close_spider(self,spider):
        self.f.close()
        print('爬虫结束了')

Crawlspider类

对于一般的scrapy.spider类,我们一般是自己在解析整个页面后获取下一页的url,然后重新发送一个请求。有时候我们想要这样做,只要满足某个条件的url,都给我们进行爬取。那么此时我们就可以通过CrawlSpider来帮助我们完成。CrawlSpiser继承自Spider,只不过是在之前的基础上增加了新的功能,可以定义爬取url的规则,以后scrapy碰到满足条件的url都进行爬取,而不用手动的yield Request

创建CrawlSpider爬虫

scrapy genspider -t crawl [爬虫名] [域名]

LinkExtractors链接提取器

使用LinkExtractors可以不用程序员自己提取想要的url,然后发送请求。这些工作都可以交给LinkExtractors,他会在所有爬的页面中找到满足规划的url,实现自动的爬取。介绍如下:

class scrapy.linkextractors.LinkExtractor(
allow = (),
deny = (),
allow_domains=(),
deny_extensions=None,
restrict_xpaths=(),
tags = ('a','area'),
attrs = ("href"),
canonicalize = True,
unique=True,
process_value =None,
deny_extensions
....
)

主要参数讲解:

  • allow:允许的url。所有满足这个正则表达式的url都会被提取。里面可以写正则表达式
  • deny:禁止的url。所有满足这个正则表达式的url都不会被提取。里面可以写正则表达式
  • allow_domains:允许的域名。只有在这个里面指定的域名的url才会被提取。里面可以写正则表达式
    -deny_domains:禁止的域名。所有在这个里面指定的域名的url都不会被提取。里面可以写正则表达式
  • estirct_xpaths=(),restricc_css()在网页哪个区域里提取链接,可以用xpath表达式和css表达式这个功能是划定提取链接的位置,让提取更加精准。
  • tags:默认提取a标签和area标签
  • attrs:接收一个属性(字符串)或者一个属性列表,提取指定的属性内的链接,默认为attrs=(‘href’,)
  • unique:就是说这个地址是不是要规定唯一的,默认true,重复收集一样的地址也没意义不是。
  • strip,这个是把地址前后多余的空格删除,很有必要。
  • process_value=None,这个作用比较强大了,他接受一个函数,可以立即对提取到的地址做加工,比如提取用js写的链接,官方文档给了一个例子。
# 例如,从这段代码中提取链接:
<a href="javascript:goToPage('../other/page.html'); return false">Link text</a>
# 你可以使用下面的这个 process_value 函数:
def process_value(value):
    m = re.search("javascript:goToPage('(.*?)'", value)
    if m:
        return m.group(1)
  • deny_extensions,排除非网页链接,默认是None,scrapy会给你排除掉以下链接。

    用LinkExtractor类提取的链接,是个link对象组成的list,每个link对象包含ur地址和链接文本。形如这样

    提取链接和文本的方法是实例.link 和实例.text
    我用拉勾网的链接提取做个例子,上代码:
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor


class YanjiuSpider(scrapy.Spider):
    name = 'yanjiu'
    allowed_domains = ['www.lagou.com']
    start_urls = ['https://www.lagou.com/']



    def parse(self, response):
        link=LinkExtractor(allow=(r'jobs/d+.html',r'zhaopin/.*?'),)
        links=link.extract_links(response)
        
        print(links)
        for lin in links:
            print(lin.url,lin.text)

Rule规则类

定义爬虫的规则类。以下是简单的介绍:

class scrapy.spiders.Rule(
    link_extractor,
    callback=None,
    cb_kwargs=None,
    follow =None,
    process_links =None,
    process_request =None
)

主要参数讲解:

  • link_extractor:一个 LinkExtractor对象,用于定义爬取规则。
  • callback:满足这个规则的url,应该要执行啷个回调函数。因为Crawlspider使用了·parse作为回调函数,因此不要覆盖parse`作为回调函数自己的回调函数。
  • follow:指定根据该规则从response中提取的链接是否需要跟进。一般为True,即新出现的页面中的url是否需要跟进
  • process_links:从`link_extractor中获取到链接的列表后会传递给这个函数,用来过滤不需要爬取的链接。
  • cb_kwargs:给回调函数传参(cb_kwargs)。
  • process_request: 该规则提取到每个request时都会调用该函数。 (用来过滤request),该函数必须返回一个request或者None。 (用来过滤request),可以用来更改request
    例如:在使用rule提取链接后,request如何添加headers、cookies?
class TestSpider(CrawlSpider):
    name = "test"
    start_urls = [
        "https://www.zhihu.com",
    ]
    myheaders = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "Accept-Encoding": "gzip,deflate",
        "Accept-Language": "en-US,en;q=0.8,zh-TW;q=0.6,zh;q=0.4",
        "Connection": "keep-alive",
        "Content-Type":" application/x-www-form-urlencoded; charset=UTF-8",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
        "Referer": "https://www.zhihu.com"
    } 
    rules = [
            Rule(LinkExtractor(allow= '/topic/d+$'),
                  process_request='request_tagPage', callback = "parse_tagPage", follow = True)
        ]


    def request_tagPage(self, request):
        # 这里可以更改Request对象中的信息,例如:body,headers,method,等等
        newRequest = request.replace(headers=self.myheaders)
        # request.repleace方法是:Create a new Request with the same attributes except for those
        given new values
        newRequest.meta.update(cookiejar=1)
        return newRequest
 
    
    def parse_tagPage(self,response):
        

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from wxapp.items import WxappItem

class WxappSpiderSpider(CrawlSpider):
name = 'wxapp_spider'
allowed_domains = ['wxapp-union.com']
start_urls = ['http://www.wxapp-union.com/portal.php?mod=list&catid=2&page=1']

#rules中可以有多个Rule,用于对提取url,并进行相关操作
rules = (
    Rule(LinkExtractor(allow=r'mod=list&catid=2&page=d+'),follow=True),
    Rule(LinkExtractor(allow=r'article-d+-d.html'), callback= 'parse_item', follow=False)
    #猜想,对于这种提取到的<a href="article-5216-1.html" </a>,scrapy会自动将其与allowed_domains(允许的域名)进行进行拼接,以形成完整的url
)
# 第一个Rule中的callback参数不需要再指定,因为只要对提取的这些url进行ruquest,然后在resposne中follow(继续)用rules进行提取url,并进行相关操作
# 第二个Rule中,follo参数为false,因为对于详情页中的url,不需要在提取并进行相关操作

# 笔记:
# 什么情况下使用follow?:使用follow:如果在爬取页面的时候,需要将满足当前条件的url再进行跟进,那么就设置为True,否则设置为False
# 什么情况下该指定callback?: 如果这个url对应的页面,只是为了获取更多的url,并不需要里面的数据,那么可以不指定callback。如果想要获取url对应页面
# 中的数据,那么就需要指定一个callback。
# 注意:这里就不要用parse方法了,因为CrawlSpider类中已经定义了parse方法
def parse_item(self, response):
    title = response.xpath('//h1[@class = "ph"]/text()').get()
    author = response.xpath('//p[@class ="authors"]/a/text()').get()
    content = "".join(response.xpath('//td[@id="article_content"]//text()').getall()).strip()
    item = WxappItem(title=title,content=content,author=author)
    yield item

#### Reuquest对象与Response对象    
1. Request 对象在我们写爬虫,爬取一页的数据需要重新发送一个请求的时候调用。这个类需要传递一些参数,其中比较常用的参数有  
- url:这个ruquest对象发送请求的url.
- callback:在下载器完相应的数据后执行的回调函数。若不指定,他会由原先的parse方法去执行。故一般request时,最好callback指定个函数,若实在不需要,可以写个pass函数。
- method:请求的方法。默认为GET方法,可以设置为其它方法还有:POST,但是对于POST方法,建议使用FormRequest来实现
- headers:请求头,对于一些固定的设置,放在settings.py中指定就可以了。对于那些非固定的,可以在发送请求的时候指定。
- meta:比较常用。用于在不同的请求之间传递数据用的。好像只能是字典。
- encoding:编码。默认的为utf-8,使用默认的就可以了。
-dot_filter:表示是否使用调度器过滤,默认为True,即默认是过滤的。在执行多次重复的请求的时候用得比较多。
-errback:在发生错误的时候执行的函数。
2.  Response对象  
Resposne对象一般是由Scrapy给你自动构建的。因此开发者不需要关心如何创建Response对象,而是如何使用他。Resposne对象有很多属性,可以用来提取数据的。主要有以下属性:  
- meta:从其他请求(Request)传过来的meta属性,可以用来保持多个请求之间的数据连接。
- encoding:返回当前字符串编码与解码的格式。
- text:将返回的数据作为unicode字符串返回,
- body:将返回的数据作为bytes字符串返回
- xpath:使用xpath选择器,来定位元素
- css:使用css选择器,来定位元素
3.  发送POST请求:  
有时候我们想要在请求数据的时候发送POST请求,那么这时候需要使用Request的子类FormRequest来实现。如果想要在爬虫一开始的时候就发动POST请求,那么需要在爬虫类中重写`start_request(self)方法(里面使用的get请求),并且不再调用`start_urls里的url.

-- coding: utf-8 --

import scrapy
class GitHubSpider(scrapy.Spider):
name = 'github_spider'
allowed_domains = ['github.com']
start_urls = ['http://github.com/']

# 这里要重写start_requests这个方法,因为scrapy默认的第一个请求是由这个方法执行的,且默认url为start_urls中的url,
# method为GET,故要重写
def start_requests(self):
    url = 'https://github.com/login'
    yield scrapy.Request(url = url,callback= self.login,meta = {'cookiejar':1})
    # 注意这里必须要用yiel
# Request对象中传递的meta好像只能是字典

def login(self,response):
    url = 'https://github.com/session'
    authenticity_token = response.xpath('//div[@id="login"]/form/input[2]/@value').get()
    form_data = {'commit': 'Sign in',
                 'utf8': '✓',
                 'authenticity_token': authenticity_token,
                 'login': 'Niceljg',
                 'password': 'ljg4667203123',
                 'webauthn-support': 'supported'
                 }
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3704.400 QQBrowser/10.4.3587.400"}
    yield scrapy.FormRequest(url= url,formdata = form_data,callback=self.parse,meta={'cookiejar':response.meta['cookiejar']},headers =headers)
    # 在需要使用post方法请求时推荐使用scrapy.FormRequest进行请求,这样能很好地提交表单信息。


def parse(self, response):
    url = 'https://github.com/settings/repositories'
    # 注意这个网站是个人设置里面的repositories
    yield scrapy.Request(url = url,meta = {'cookiejar':True},dont_filter= True,callback=self.get_repositories)

def get_repositories(self,response):
    projects_names= response.xpath('//div[@class="listgroup-item simple public fork js-collab-repo"]//a[@class = "mr-1"]/text()').getall()
    for i in projects_names:
        print(i)

注意:debug信息中出现

D:Anaconda3libsite-packagesscrapyspidermiddlewares eferer.py:284: RuntimeWarning: Could not load referrer policy 'origin-when-cross-origin, strict-origin-when-cross-origin'

warnings.warn(msg, RuntimeWarning)

是正常的,猜想是提醒你重写了start_requests方法了吧

#### 下载文件与图片
scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的item pipelines。这些pipeline有些共同的方法和结构(我们称之为media pipeline)。一般来说你会使用Files Pipelines或者Images Pipeline。  

1. 为什么要选择使用`scrapy`内置的下载文件的方法:
- 避免重新下载最近已经下载过的数据。
- 可以方便的指定文件存储的路径
- 可以将下载的图片转换成通用的格式。比如png或jpg
- 可以方便的生成缩略图。
- 可以方便的检测图片的宽和高,确保他们满足最小限制
- 异步下载,效率非常高

2. 下载文件的Files Pipeline:
当使用`Files Pipeline`下载文件的时候,按照以下步骤完成:
- 定义好一个`Item`,然后在这个`Item`中定义两个属性,分别为`files_urls`以及`files`。`files_urls`是用来存储需要下载的图片的url链接,需要给一个列表。
- 当文件下载完成后,会把文件下载的相关信息存储到`tem`的`files`属性中。比如下载路径、下载的url和文件的校验码等。
- 在配置文件`settings.py`中配置FILES_STORE,这个配置是用来设置文件下载下来的路径。
-
原文地址:https://www.cnblogs.com/greatljg/p/11160250.html