scrapy框架的介绍与基本应用

1. scrapy的概念及工作流程

1.1 概念及安装
scrapy是基于异步模块twisted的爬虫框架,集成了爬虫项目中通用性较高的部分功能,具备高性能的数据解析,请求发送,持久化存储,全站数据爬取,中间件,分布式等
环境的安装:

  - mac、linum:pip install scrapy
  - windows:
      - a. pip3 install wheel
      - b. 下载twisted文件,下载地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
      - c. 进入下载目录,执行 pip install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
   - Twisted:就是一个异步的架构。被作用在了scrapy中。
   - 安装报错:需要更换另一个版本的twisted文件进行安装即可。
       - d. pip install pywin32
       - e. pip install scrapy
   - 测试:cmd中scrapy按下回车,如果没有报错说明安装成功。

1.2 scrapy的组件及工作流程

引擎(Scrapy):
驱动整个系统的运行,处理其他组件的数据流,触发事务(框架核心);
调度器(Scheduler):
接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址;
下载器(Downloader):
接收调度器经引擎发送的下载请求,完成从网络获取数据的任务(Scrapy下载器是建立在twisted这个高效的异步模型上的);
爬虫(Spiders):
处理主要的业务,决定爬取的内容,发送地址url给到引擎,接收下载器获取的数据,主要定制部分;
项目管道(Pipeline):
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据,也是主要定制的组件;
中间件(Middlewares):
下载器中间件可扩展发送请求过程的规则,如添加ip代理,ua池等,
爬虫中间件可修改响应,基本无用处。

scrapy框架的介绍与基本应用_第1张图片
1.3 使用流程

创建项目:
scrapy startproject testproject
创建爬虫:
cd testproject
scrapy gen testspider "www.test.com"
spider代码写入获取数据:
pipline代码写入保存数据:
数据建模Item写入获取的信息: 
运行代码:
scrapy crawl testspider

1.4 定制组件案例

下载器中间件:
设置ua池及ip代理池

import random
import requests
import base64
import json
# UA池
class UserAgentDownloadMiddleware(object):
    USER_AGENT = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
        "Mozilla/5.0 (X11; Linux i686; rv:64.0) Gecko/20100101 Firefox/64.0",
        "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0",
        "Mozilla/5.0 (X11; Linux i586; rv:63.0) Gecko/20100101 Firefox/63.0",
        "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:63.0) Gecko/20100101 Firefox/63.0"
    ]
    def process_request(self,request,spider):
        user_agent=random.choice(self.USER_AGENT)
        request.headers['User-Agent']=user_agent

# IP proxy池
class IpPoxyDownloadMiddleware(object):
    # 开放代理
    PROXIES = [
    "178.44.170.152:8080", "110.44.113.182:8080",
    "209.126.124.73:8888", "84.42.79.243:8080",
    "117.97.31.180:8080", "103.76.199.166:8080"]
    def process_request(self, request, spider):
    	proxy=random.choice(self.PROXIES)
        request.meta['proxy']=proxy

    # 独享代理
     PROXIES_URL=""
    def process_request(self, request, spider):
        proxy = '121.199.6.124:16816'
        user_password = ""
        request.meta['proxy'] = proxy
        # bytes
        b64_user_password = base64.b64encode(user_password.encode('utf-8'))
        # 设置认证
        request.headers['Prox-Authorzation'] = 'Basic' + b64_user_password.decode('utf-8')

    def process_request(self,request,spider):
        pass
    def process_response(self,request,response,spider):
        pass
    def get_proxy(self):
        response=requests.get(self.PROXIES_URL)
        text=response.text
        result=json.loads(text)
        data=result['data'][0]

使用selenium修改下载中间件处理ajax数据,也可通过selenium登录获取cookie:

from selenium import webdriver
import time
from scrapy.http.response.html import HtmlResponse
class SeleniumDownloadMiddleware(object):
	def __init__(self):
	    # 实例化driver对象
		self.driver=webdriver.Chrome(executable_path=r"")

	def process_request(self,request,spider):
		# 对起始url发起请求
		self.driver.get(request.url)
		time.sleep(1)
		try:
			while True:
				showmore=self.driver.find_element_by_class_name('show-more')
				showmore.click()
				time.sleep(0.3)
				if not showmore:
					break
		except:
			pass
		# 获取源代码
		source=self.driver.page_source
		# 返回selenium请求后的响应
		response=HtmlResponse(url=self.driver.current_url,body=source,request=request,encoding='utf-8')
		return response

Item pipline:
存储方式:

import pymysql
# mysql同步存储
class JianshuSpiderPipeline(object):
    def __init__(self):
        dbparams={
     
            'host':'172.0.0.1',
            'port':3306,
            'user':'root',
            'password':'123456',
            'charset':'utf8'
        }
        # 创建一个连接对象
        self.conn=pymysql.connect(**dbparams)
        # 获取游标
        self.cursor=self.conn.cursor()
        self._sql=None

    def process_item(self, item, spider):
    	# 执行sql语句
        self.cursor.execute(self.sql,(item['title'],item['content'],
item['author'],item['avatar'],item['pub_time'],item['origin_url'],item['article']))
		# 确认
        self.conn.commit()
        return item
    # 魔法方法,构建sql语句
    @property
    def sql(self):
        if not self._sql:
            self._sql="""
            insert into article(id,title,content,author,avatar,pub_time,
            origin_url,article_id) values(null,%s,%s,%s,%s,%s,%s,%s)
            """
            return self._sql
        return self._sql

from pymysql import cursors
from twisted.enterprise import adbapi
# mysql异步存储
class JianshuTwsitedSpiderPipeline(object):
    def __init__(self):
        dbparams={
     
            'host':'172.0.0.1',
            'port':3306,
            'user':'root',
            'password':'123456',
            'charset':'utf8',
            'cursorclass':cursors.DictCursor
        }
        # 创建连接池
        self.dbpool=adbapi.ConnectionPool('pymysql',**dbparams)
        self._sql=None

    @property
    def sql(self):
        if not self._sql:
            self._sql="""
            insert into article(id,title,content,author,avatar,pub_time,
            origin_url,article_id) values(null,%s,%s,%s,%s,%s,%s,%s)
            """
            return self._sql
        return self._sql

    def process_item(self,item,spider):
        # 启动连接池,异步插入sql语句
        defer=self.dbpool.runInteraction(self.insert_item,item)
        defer.addErrback(self.handler_error,item,spider)

    def insert_item(self,cursor,item):
        cursor.execute(self.spl,(item['title'],item['content'],
item['author'],item['avatar'],item['pub_time'],item['origin_url'],item['article']))

    def handler_error(self,error,item,spider):
        print("="*10+'error'+"="*10)
        print(error)
        print("=" * 10 + 'error' + "=" * 10)

# 以json格式存储数据
from scrapy.exporters import JsonLinesItemExporter
class BossSpiderPipeline(object):
    def __init__(self,):
        self.fp=open('boss.json','wb')
        # 实例化一个写入对象 
        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()

大文件存储:
from urllib import request
import os
from scrapy.pipelines.images import ImagesPipeline
from bmw_img import settings

class BmwImgPipeline(object):
    def __init__(self):
        # 构建图片保存的路径
        self.path=os.path.join(os.path.dirname(os.path.dirname(__file__)),'images')
        if not os.path.exists(self.path):
            os.mkdir(self.path)
    def process_item(self, item, spider):
        category=item['category']
        urls=item['urls']
        # 构建分类保存路径
        category_path=os.path.join(self.path,category)
        if not os.path.exists(category_path):
            os.mkdir(category_path)
        for url in urls:
            image_name=url.split('_')[-1]
            # 下载保存在本地
            request.urlretrieve(url,os.path.join(category_path,image_name))
        return item

# 重写下载规则,实现分类保存图片
from . import settings
from scrapy.pipelines.images import ImagesPipeline
import os
import re

# 继承图片下载的pipline
class ZcoolPicturePipeline(ImagesPipeline):
    def get_media_requests(self, item, info):
        """获取item并绑定在request上传递给file_path方法"""
        # 获取所有请求
        media_requests=super(ZcoolPicturePipeline, self).get_media_requests(item,info)
        for media_request in media_requests:
            # 将item绑定给request
            media_request.item=item
        return media_requests

    def file_path(self, request, response=None, info=None):
        """重构保存路径,实现分类存储"""
        # 原始pipline的存储路径
        origin_path=super(ZcoolPicturePipeline,self).file_path(request,response,info)
        title=request.item.get('title')
        # 去掉文件夹中不允许存在的字符
        title=re.sub(r'[\\/:\*\?"<>\|]',"",title)
        save_path=os.path.join(settings.IMAGES_STORE,title)
        if not os.path.exists(save_path):
            os.mkdir(save_path)
        # 去除原始存储路径得到文件名称
        image_name=origin_path.replace("full/",'')
        save_path=os.path.join(save_path,image_name)
2.crawl spider及分布式组件spider redis的使用流程

2.1 crawl的使用案例

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import ZcoolPictureItem

class ZcoolSpider(CrawlSpider):
    name = 'zcool'
    allowed_domains = ['www.zcool.com.cn']
    start_urls = ['https://www.zcool.com.cn/discover/0!0!0!0!0!!!!-1!0!1']
    # 自动匹配出需要请求的url
    rules = (
        Rule(LinkExtractor(allow=r'.+0!0!0!0!0!!!!-1!0!\d+/'), follow=True),
        Rule(LinkExtractor(allow=r'.+/work/.+html'),callback='parse_detail',follow=False)
    )
    # 提取数据
    def parse_detail(self,response):
        title=response.xpath("//div[@class='details-contitle-box']/h2//text()").getall()
        title=''.join(title).strip()
        img_url=response.xpath("//div[@class='photo-information-content']/img/@src").getall()
        item=ZcoolPictureItem(title=title,img_url=img_url)
        yield item

2.2 分布式爬虫介绍
需要搭建一个分布式的机群,让后让机群中的每一台电脑执行同一组程序,让其对同一组资源进行联合且分布的数据爬取。
实现方式:scrapy+redis(scrapy结合着scrapy-redis组件)
原生的scrapy框架因为无法共享调度器与管道所以不能实现分布式;
如何实现分布式:使用scrapy-redis组件即可,给原生的scrapy框架提供共享的管道和调度器;
实现流程:
导包:from scrapy_redis.spiders import RedisCrawlSpider
修改当前爬虫类的父类为:RedisCrawlSpider
将start_url替换成redis_keys的属性,属性值为任意字符串
redis_key = ‘xxx’:表示的是可以被共享的调度器队列的名称,最终是需要将起始的url手动放置到redis_key表示的队列中
对settings.py进行配置:

指定调度器:
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True
指定管道:
ITEM_PIPELINES = {
     
            'scrapy_redis.pipelines.RedisPipeline': 400
        }
特点:该种管道只可以将item写入redis
指定redis:
        REDIS_HOST = 'redis服务的ip地址'
        REDIS_PORT = 6379

配置redis的配置文件(redis.window.conf):

解除默认绑定
56行:#bind 127.0.0.1
关闭保护模式
75行:protected-mode no
启动redis服务和客户端
执行scrapy工程(不要在配置文件中加入LOG_LEVEL)
程序会停留在listening位置:等待起始的url加入
向redis_key表示的队列中添加起始url
需要在redis的客户端执行如下指令:(调度器队列是存在于redis中)
lpush sunQueue http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1

2.3 分布式实现案例

爬虫部分:
# -*- coding: utf-8 -*-
import scrapy
from ..items import LianjiaItem
import copy
# crawlspider继承RedisCrawlSpider
from scrapy_redis.spiders import RedisSpider

class LjSpiderSpider(RedisSpider):
    name = 'lj_spider'
    allowed_domains = ['www.lianjia.com']
    # 分布式需要修改为redis_key作为起始钥匙
    redis_key= 'lj'

    def parse(self, response):
        """获取到每个城市的url"""
        item = LianjiaItem()
        pro_list = response.xpath("//div[@class='city_province']")
        for pro in pro_list:
            item['provience']= pro.xpath("./div/text()").get()
            cities=pro.xpath("./ul//li//a")
            for city in cities:
                city_name=city.xpath("./text()").get()
                city_url=city.xpath("./@href").get()
                # print(item['provience'],item['city_name'],city_url)
                item['city_name']=city_name
                # print(city_dict)
                # 发起get请求,meta参数需深拷贝防止数据被冲掉
                yield scrapy.Request(url=city_url+"zufang",callback=self.get_area_url,meta={
     'item':copy.deepcopy(item)},dont_filter=True)

    def get_area_url(self,response):
        """获取每个城市每个区的url"""
        item=response.meta.get('item')
        # print(item)
        area_list=response.xpath("//div[@class='filter']//ul[2]/li")[1:]
        for area in area_list:
            item['area_name']=area.xpath("./a/text()").get()
            # area_name = area.xpath("./a/text()").get()
            area_url=area.xpath("./a/@href").get()
            url=response.urljoin(area_url)
        	yield item
设置部分:
# 修改调度器,确保request能够共享
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 使所有爬虫共享相同的去重指纹
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 设置redis为item pipeline,redis数据库作为数据存储的库
ITEM_PIPELINES = {
     
      'scrapy_redis.pipelines.RedisPipeline': 300
}
# 保证所有进入对列的数据不被清理,可实现断点续爬
SCHEDULER_PERSIST = True
# 设置连接redis信息
REDIS_HOST = ''
REDIS_PORT = 6379        	
3.scrapy_splash js模拟执行组件及爬虫管理工具scrapyd的使用

3.1 splash的应用
作用:scrapy-splash能够模拟浏览器加载js,并返回js运⾏后的数据
安装流程:
使用docker进行安装:splash-dockerfile=>拉取镜像:sudo docker pull scrapinghub/splash=>验证:前台: sudo docker run -p 8050:8050 scrapinghub/splash 后台: sudo docker run -d -p 8050:8050 scrapinghub/splash=>访问查看http://127.0.0.1:8050
配置:

# 渲染服务的url 
SPLASH_URL = 'http://127.0.0.1:8050' 
# 下载器中间件 
DOWNLOADER_MIDDLEWARES = {
      
'scrapy_splash.SplashCookiesMiddleware': 723, 
'scrapy_splash.SplashMiddleware': 725, 
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMi ddleware': 810, }
# 去重过滤器 
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' 
# 使⽤Splash的Http缓存 
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage' 
# Obey robots.txt rules 
ROBOTSTXT_OBEY = False
使用案例:
import scrapy 
from scrapy_splash import SplashRequest 
# 使⽤scrapy_splash包提供的r equest对象 
class WithSplashSpider(scrapy.Spider): 
	name = 'with_splash' 
	allowed_domains = ['baidu.com'] 
	start_urls = ['https://www.baidu.com/s?wd=13161933309'] 
	def start_requests(self): 
		yield SplashRequest(self.start_urls[0], callback=self.parse_splash, args={
     'wait': 10}, endpoint='render.html') # 使⽤splash服务 的固定参数 
		def parse_splash(self, response): 
			with open('with_splash.html', 'w') as f: 
				f.write(response.body.decode())

3.2 scrapyd部署scrapy项⽬
介绍:scrapyd是⼀个⽤于部署和运⾏scrapy爬⾍的程序,它允许你通过JSON API 来部署爬⾍项⽬和控制爬⾍运⾏,scrapyd是⼀个守护进程,监听爬⾍的运⾏ 和请求,然后启动进程来执⾏它们。

安装:
scrapyd服务端: pip install scrapyd scrapyd
客户端: pip install scrapyd-client
启动:sudo scrapyd
访问:127.0.0.16800

部署:
在项目目录下找出scrapy.cfg文件
scrapy框架的介绍与基本应用_第2张图片下载终端命令:curl命令

部署爬虫,爬虫根目录下执行:scrapyd-deploy 部署名 -p 项⽬名称
启动项⽬: 
curl http://localhost:6800/schedule.json -d project=project_name -d spider=spider_name
关闭爬⾍: 
curl http://localhost:6800/cancel.json -d project=project_name -d job=jobid
列出项⽬:
curl http://localhost:6800/listprojects.json 
列出爬⾍:
curl http://localhost:6800/listspiders.json?project=myspider
列出job:
curl http://localhost:6800/listjobs.json?project=myspider 
终⽌爬⾍(该功能会有延时或不能终⽌爬⾍的情况,此时 可⽤kill -9杀进程的⽅式中⽌):
curl http://localhost:6800/cancel.json -d project=myspider -d job=tencent 

使用requests模块控制:

import requests 
# 启动爬⾍ 
url = 'http://localhost:6800/schedule.json' 
data = {
      
	'project': 项⽬名, 
	'spider': 爬⾍名, }
resp = requests.post(url, data=data) 

# 停⽌爬⾍ 
url = 'http://localhost:6800/cancel.json' 
data = {
      
	'project': 项⽬名, 
	'job': 启动爬⾍时返回的jobid, }
resp = requests.post(url, data=data)

3.3 Gerapy部署scrapy项目

安装Gerapy
pip install gerapy
初始化gerapy
gerapy init
初始化数据库
gerapy migrate
默认使用的是SQlite数据库
运行gerapy服务
gerapy runserver
访问Gerapy管理界面
http://127.0.0.1:8000

部署项目:
可直接将scrapy中的项目添加进去或者手动将爬虫文件移动到初始化包下的project文件夹

你可能感兴趣的:(python,分布式)