Python爬虫-5 scrapy框架

1 安装

pip install scrapy

2 框架组成

引擎(engine)

自动运行,无需关注,会自动组织所有的请求对象,分发给下载器

下载器(downloader)

从引擎处获取到请求对象后,请求数据

爬虫spiders

scrapy.Spider 普通的爬虫
scrapy.CrawlSpider
	可设置规则的爬虫类
	Rule 规则类
开始的函数
	start_requests()

调度器(scheduler)

管道(Item pipeline)

1. 清理HTML数据
2. 验证爬取的数据(检查item包含某些字段)
3. 查重(并丢弃)
4. 将爬取结果保存到数据库中
5. 对图片数据进行下载

3 工作原理

流程
1、爬虫引擎获得初始请求开始抓取。
2、爬虫引擎开始请求调度程序,并准备对下一次的请求进行抓取。
3、爬虫调度器返回下一个请求给爬虫引擎。
4、引擎请求发送到下载器,通过下载中间件下载网络数据。
5、一旦下载器完成页面下载,将下载结果返回给爬虫引擎。
6、引擎将下载器的响应通过中间件返回给爬虫进行处理。
7、爬虫处理响应,并通过中间件返回处理后的items,以及新的请求给引擎。
8、引擎发送处理后的items到项目管道,然后把处理结果返回给调度器,调度器计划处理下一个请求抓取。
9、重复该过程(继续步骤1),直到爬取完所有的url请求

4 如何使用

创建项目
	终端输入scrapy startproject  项目名称
目录结构
	spiders
		__init__.py
		自定义的爬虫文件.py
	__init__.py
	items.py
	middlewares.py
	pipelines.py
	settings.py
创建自定义爬虫文件
	scrapy genspider 爬虫名字 网页的域名
	继承scrapy.Spider类
		name = 'qiubai'
		allowed_domains
		start_urls
		parse(self, response)
			response 是 scrapy.http.HtmlResponse类对象
			response的属性
				response.encoding
					字符集,不能直接修改
				response.text
					文本信息
				response.body
					字节数据
				response.headers
					头
				response.meta
					元信息
					用于从请求向响应的解析函数中传递参数
				response.request
					响应的哪一个请求对象
				response.url
			response.xpath()/css()
				scrapy.selector.Selector
				返回Selector对象
				内部写法:self.selector.xpath()
				css()用法
					同css的样式选择器,如id,class或标签等
					访问标签内的属性
						'#page a::attr("href")'
							获取id为page的a标签的href属性
						'.list-img a::text'
							获取class为list-img下的a标签的text文本内容
				xpath()用法
					同lxml的xpath用法,主要是针对的路径
				提取数据
					extract()
						Selector对象的方法,用于获取Selector对象的内容
						response.xpath('//title/text()').extract() 
							返回list
						response.css('').xpath()
							先使用css选择标签元素,再通过xpath提取内容
					extract_first()
						提取第一条内容
					get()
						同 extract_first()方法
	修改settings.py代码
		ROBOTSTXT_OBEY设置为False。默认是True。即遵守机器协议,那么在爬虫的时候,scrapy首先去找robots.txt文件,如果没有找到。则直接停止爬取。
		DEFAULT_REQUEST_HEADERS添加User-Agent。这个也是告诉服务器,我这个请求是一个正常的请求,不是一个爬虫。
	解析数据回传给engine
		yield item
	向engine发起新的请求
		yield scrapy.Request(url, callback=,meta=, dont_filter=True)
	运行程序
		scrapy crawl 爬虫名称
		导出文件
			-o name.json
			-o name.xml
			-o name.csv
			eg
				scrapy crawl dy -o vides.json
items.py
	import scrapy

class QsbkItem(scrapy.Item):
author = scrapy.Field()
content = scrapy.Field()
pipeline.py
open_spider
process_item
close_spider
JsonItemExporter
JsonLinesItemExporter
运行scrapy项目
from scrapy import cmdline

cmdline.execute(“scrapy crawl qsbk”.split())

5 保存数据的流程

1、在爬虫文件的 parse 方法中,只用来获取数据,不要做保存数据的操作,因为这样用不到多线程

所以要通过 yield 把封装的数据 交给 pipeline

2、在 配置文件中, 把 pipeline 的配置打开

ITEM_PIPELINES = {
    
    
    'douban.pipelines.DoubanPipeline': 300,
    
    # 如果有多个 pipeline路径,那么这些pippeline都会去 处理 yield 返回的数据
    # 值代表的是 权重, 值越小 越先执行
    'douban.pipelines.DoubanPipeline1': 310,
}

3、在 pipeline 文件中, 定义如下方法

class DoubanPipeline:
    # open_spider 在爬虫运行 开始的时候 先执行,相当于 __init__ 方法
    # 参数spider 的作用是,区分是哪个爬虫
    
    
    # 比如现在有两个爬虫
    '''
    spider
       __init__.py
       movie.py # 爬虫
       rank.py  # 爬虫
    '''
    def open_spider(self, spider):
        if spider.name == 'movie':
        	self.fp = open('movie.json', 'a', encoding='utf-8')
        elif spider.name == 'rank':
            self.fp1 = open('rank.json', 'a', encoding='utf-8')

    def process_item(self, item, spider):
        if spider.name == 'movie':
        	self.fp.write(json.dumps(item, ensure_ascii=False))
        elif spider.name == 'rank':
            self.fp1.write(json.dumps(item, ensure_ascii=False))
        return item
	
    # 爬虫结束后执行,相当于 __del__方法
    def close_spider(self, spider):
        if spider.name == 'movie':
        	self.fp.close()
        elif spider.name == 'rank':
            self.fp1.close()
            
            
# 一般做法是 在一个项目中 创建一个 爬虫, 那就没有必要 去判断 爬虫

如果要把数据通过json形式 保存在文件的话,那么 pipeline 文件中,应该使用 JsonLinesItemExporter 方法

from scrapy.exporters import JsonLinesItemExporter
class DoubanPipeline:
    def open_spider(self, spider):
        self.fp = open('douban.json', 'ab')
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

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

    def close_spider(self, spider):
        self.fp.close()

在爬虫文件中,请求其他的链接地址

  • 在 parse 方法中,通过 yield scrapy.Request 方法请求地址, Request 方法中有两个必填的参数

    参数:

    • url:请求的网址
    • callback:回调函数,当爬虫获取到请求的源代码之后,会交给 callback 定义的方法来处理数据
        def parse(self, response):
    
            # 爬取 列表页面中,每个新闻 的 内容页的链接
            # 访问 每个内容页的 链接,获取数据
     
            # response.urljoin 在不完整的地址前面补充 域名
            hrefs = [response.urljoin(href) for href in response.xpath('//*[@id="itemContainer"]/div/div/h3/a/@href').getall()]
    
            for href in hrefs:
                # 怎么用 scrapy 去访问每一个连接地址
                yield scrapy.Request(url=href, callback=self.parse_detail)
                
                
        def parse_detail(self, response):
    
            title = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get()
            desc = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[3]/p/text()').get()
    
            item = WxappItem(title=title, desc=desc)
    
            yield item
    

传递请求的参数

方法: scrapy.Request( url, callback, meta={‘k’:‘v’, ‘k’: ‘v’}) meta就是传递的参数,字典形式

获取传递的参数

方法:response.meta.get(‘k’) 通过响应对象的 response 的 meta 属性,格式是字典

如果项目初始的请求方式就是 post 的话, 那么需要 在 爬虫文件中 重写 start_request 方法

def start_requests(self):
    data = {
    
    
        'kw': 'world'
    }
    yield scrapy.FormRequest(url=self.start_urls[0], formdata=data, callback=self.post_data)

scrapy中 中间件的使用,scrap有两种中间件,一个是爬虫中间件(这个一般用不到), 一个是 下载器 中间件。中间件,用的比较多的方法是 process_request, 这个方法是设置 请求头中的一些参数,和返回响应的 自定义的 源代码, 一般最常用的就是 设置 ip 代理 和 随机 请求头

  • ip代理的设置方式

    class downloader1:
    
        proxy_list = [
            '119.132.76.144:4256',
            '182.135.158.137:4226',
            '42.242.122.222:4243'
        ]
    
        def process_request(self, request, spider):
         
            data = random.choice(self.proxy_list)
            request.meta['proxy'] = f'http://{data}'
          
    
        def process_response(self, request, response, spider):
            
            return response
    
  • 随机请求头的方式

    class downloader2:
        USER_AGENTS = [
    
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
    
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36",
    
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
    
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
    
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36",
    
        ]
    
        def process_request(self, request, spider):
            data = random.choice(self.USER_AGENTS)
            request.headers['user-agent'] = data
    
    
        def process_response(self, request, response, spider):
            return response
    
    

注意:一定要在 配置文件中 打开 中间件的配置

DOWNLOADER_MIDDLEWARES = {
    
    
   # 'httpbin.middlewares.HttpbinDownloaderMiddleware': 543,
   # 'httpbin.middlewares.downloader1': 543,
   'httpbin.middlewares.downloader2': 544,
}

了解: 如果有多个中间件,那么中间件中 方法的执行顺序

  • process_request 返回 None

    process_request 的方法是 配置文件中 中间件定义 顺序的 从上往下 执行

    process_resposne 的方法是 从下往上 执行

  • process_request 返回响应对象

    不会再执行 下一个 中间件的 process_request 方法,

    process_response 方法 还是 从下往上执行

    爬虫中 获取的 源代码是 响应对象中设置的 代码,比如

    def process_request(self, request, spider)
    	return HtmlResponse(url=request.url, body=b'返回的内容', request=request)
    

在scrapy中使用selenium

在中间件中设置

# 通过seelnium拿到网站的源码
# 再通过 process_request 返回响应对象的同时,把源码加入到 响应的body
class SeleniumMiddware:
    def __init__(self):
        chrome_path = chrome_path = r'C:\Users\apple\Desktop\soft\chromedriver.exe'
        self.driver = webdriver.Chrome(executable_path=chrome_path)

    def process_request(self, request, spider):

        self.driver.get(request.url)
        data = self.driver.page_source

        return HtmlResponse(url=request.url, body=data, request=request, encoding='utf-8')

6 规则爬虫的流程

  • 创建规则爬虫

    • 创建项目
    • 创建规则爬虫:scrapy genspider -t crawl 爬虫名称 域名
  • 最重要的就是创建 正则去 匹配 要请求的网址

    class AppSpider(CrawlSpider):
        name = 'app'
        allowed_domains = ['dreawer.com']
        start_urls = ['http://wxapp.dreawer.com/portal.php?mod=list&catid=2&page=1']
    	
        
        # 重要
        rules = (
            # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面
            # callback 把请求到的页面源码交给 callback 方法去 获取特定数据
            # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则
            # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则
            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),
        )
    

scrapy-redis 项目的搭建,比如以 爬取 小程序社区 为例

  • 创建一个普通的规则爬虫

    • 创建项目
    • 创建爬虫 scrapy genspider -t crawl 爬虫名称 域名
  • 在 爬虫程序里面去写 爬虫规则

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class AppSpider(CrawlSpider):
        name = 'app'
        allowed_domains = ['dreawer.com']
        start_urls = ['http://wxapp.dreawer.com/portal.php?mod=list&catid=2&page=1']
    
        rules = (
            # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面
            # callback 把请求到的页面源码交给 callback 方法去 获取特定数据
            # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则
            # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则
            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),
        )
    
        def parse_item(self, response):
            con = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get()
            print(con)
    
  • 能够 让这个 规则爬虫 先 运行起来

  • 修改 爬虫中 的一些设置,让爬虫能够使用 scrapy_redis 的 逻辑

    • 把 爬虫继承的 CrawlSpider父类 改成 RedisCrawlSpider

    • 去掉 start_urls

    • 在 爬虫类 添加 redis_key 属性,可以把这个属性 看成 redis 队列的 名称,它的值一般叫做 redis_key = ‘start:url’

      整个爬虫类 应该是一下写法

      import scrapy
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from scrapy_redis.spiders import RedisCrawlSpider
      
      from ..items import AppredisItem
      
      
      class AppSpider(RedisCrawlSpider):
          name = 'app'
          allowed_domains = ['dreawer.com']
          # start_urls = ['http://dreawer.com/']
          redis_key = 'start:url'
      
          rules = (
              # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面
              # callback 把请求到的页面源码交给 callback 方法去 获取特定数据
              # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则
              # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则
              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),
          )
      
          def parse_item(self, response):
              title = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get()
              desc = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[3]/p/text()').get()
      
              item = AppredisItem(
                  title=title,
                  desc=desc
              )
      
              yield item
      
  • 修改 配置文件

    • 启用Redis调度存储请求队列: 添加配置是 SCHEDULER = "scrapy_redis.scheduler.Scheduler"

    • 确保所有的爬虫通过Redis去重: 添加配置是 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

    • 处理yield 返回的数据,是通过 scrapy_redis默认的pipeline进行,这个pipeline默认是把数据保存在 redis的队列中

      添加配置:

      ITEM_PIPELINES = {
              
              
          'scrapy_redis.pipelines.RedisPipeline': 300
      }
      
    • 不清除Redis队列、这样可以暂停/恢复 爬取: 添加配置 SCHEDULER_PERSIST = True

    • 指定连接到redis时使用的端口和地址:

      添加配置

      REDIS_HOST = '127.0.0.1'
      REDIS_PORT = 6379
      
  • 开启 redis 服务器, 找到 redis 的安装目录, 双击 下面的 redis-server.exe 程序

  • 如果有多个 服务器,那么就需要把 项目代码 放入到 多个服务器中,进入项目,执行爬虫. 注意 redis的ip应该是 存放 redis服务的 外网 ip, 状态是阻塞的,因为没有获取到第一个 url

  • 打开 redis 的客户端, 找到 redis 的安装目录,点击下面的 redis-cli.exe 的程序

  • 添加一个 redis的 队列,名字叫做 start:url , 值就是 第一个要 爬起的 页面, 命令就是 lpush start:url http://wxapp.dreawer.com/portal.php?mod=list&catid=2

  • 添加完成之后, 所有的爬虫 由 阻塞状态 就会变成 爬取数据的 状态

  • 爬取数据后,在 redis 里面会创建 3个 变量

    • dupefilter: 保存去重的网址
    • items: 保存的是爬取的数据
    • requests: 即将要 爬取的网址

猜你喜欢

转载自blog.csdn.net/hmh4640219/article/details/115184218
今日推荐