爬虫之scrapy框架

1.scrapy框架介绍

  Scrapy是用纯Python实现的一个为了爬取网站数据、提取结构性数据而编写的应用框架html

  Scrapy 特点是使用了 Twisted异步网络框架来处理网络通信,加快了下载速度,不用本身去实现异步框架,而且包含了各类中间件接口,能够灵活的完成各类需求python

1.1 scrapy框架架构图

Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通信,信号、数据传递等。git

Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照必定的方式进行整理排列,入队,当引擎须要时,交还给引擎。github

Downloader(下载器):负责下载Scrapy Engine(引擎)发送的全部Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,ajax

Spider(爬虫):它负责处理全部Responses,从中分析提取数据,获取Item字段须要的数据,并将须要跟进的URL提交给引擎,再次进入Scheduler(调度器),mongodb

Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.shell

Downloader Middlewares(下载中间件):你能够看成是一个能够自定义扩展下载功能的组件。数据库

Spider Middlewares(Spider中间件):你能够理解为是一个能够自定扩展和操做引擎和Spider中间通讯的功能组件(好比进入Spider的Responses;和从Spider出去的Requests)segmentfault

注意!只有当调度器中不存在任何request了,整个程序才会中止,(也就是说,对于下载失败的URL,Scrapy也会从新下载。)浏览器

官方文档0.25连接

最新官方文档1.5连接

以上参考连接

1.2 scrapy安装

#Windows平台
    1、pip3 install wheel 
    #安装后,便支持经过wheel文件安装软件
    3、pip3 install lxml
    4、pip3 install pyopenssl
    五、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/
    # 根据电脑Python版本和位数下载并安装最新版的pywin32,它会自动寻找Python的安装路径,因此不须要作任何修改,一直单击【下一步】便可。
    # 这里有时候会报中止工做,可是经过pip3 list 命令能够看到他已经存在
    六、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
    七、执行pip3 install 下载目录路径\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    # Scrapy须要依赖Twisted。Twisted是Python下面一个很是重要的基于事件驱动的IO引擎。Twisted的安装依赖于pywin32
    8、pip3 install scrapy
  
#Linux平台
    一、pip3 install scrapy

# 这里安装好pywin32后,后期可能仍是没法运行(报ImportError: DLL load failed错误),这里通常都是咱们该模块安装的有问题致使的,这里能够参考知乎或者参考Stack Overflow

个人是参考Stack Overflow下的说明,执行了 pip install win10toast --ignore-installed 才好的

1.3 命令行相关指令

# 1 查看帮助
    scrapy -h
    scrapy <command> -h

# 2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不须要
    Global commands:
        startproject #建立项目
        genspider    #建立爬虫程序
        settings     #若是是在项目目录下,则获得的是该项目的配置
        runspider    #运行一个独立的python文件,没必要建立项目
        shell        #scrapy shell url地址  在交互式调试,如选择器规则正确与否
        fetch        #独立于程单纯地爬取一个页面,能够拿到请求头
        view         #下载完毕后直接弹出浏览器,以此能够分辨出哪些数据是ajax请求
        version      #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本
    Project-only commands:
        crawl        #运行爬虫,必须建立项目才行,确保配置文件中ROBOTSTXT_OBEY = False
        check        #检测项目中有无语法错误
        list         #列出项目中所包含的爬虫名
        edit         #编辑器,通常不用
        parse        #scrapy parse url地址 --callback 回调函数  #以此能够验证咱们的回调函数是否正确
        bench        #scrapy bentch压力测试

# 3 官网连接
    https://docs.scrapy.org/en/latest/topics/commands.html

1.4 框架结构

'''
project_name/                
   scrapy.cfg              # 项目的主配置信息,用来部署scrapy时使用
   project_name/
       __init__.py
       items.py            # 设置数据存储模板,用于结构化数据,如:Django的Model
       pipelines.py        #  数据处理行为,如:通常结构化的数据持久化
       settings.py         # 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写不然视为无效,正确写法USER_AGENT='xxxx'
       spiders/            # 爬虫目录,如:建立文件,编写爬虫规则
           __init__.py
           爬虫1.py      # 爬虫程序1
           爬虫2.py
           爬虫3.py

'''

1.5 项目流程

新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
明确目标 (编写items.py):明确你想要抓取的目标
制做爬虫 (spiders/xxspider.py):制做爬虫开始爬取网页
存储内容 (pipelines.py):设计管道存储爬取内容

1.6 启动一个项目

1 scrapy startproject DianShang   # 建立爬虫项目
2 scrapy genspider jd jd.com      # 生成一个爬虫程序
3 scrapy crawl jd                 # 运行scrapy项目

如今咱们有了基本的项目骨架

1.7 spider类的说明

  Spiders是为站点爬网和解析页面定义自定义行为的地方

spiders下的jd.py文件

# -*- coding: utf-8 -*-
import scrapy


class JdSpider(scrapy.Spider):
    name = 'jd'
    allowed_domains = ['jd.com']
    start_urls = ['http://jd.com/']

    def parse(self, response):
        pass

Spider类中的start_requests方法:

    def start_requests(self):
        cls = self.__class__
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won't be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)
View Code

  有时候后台的start_requests方法会访问不到咱们的目标站点,咱们大多数状况须要本身构造此方法,它的最终返回值是用生成器yield返回的,咱们本身写也建议使用生成器。

start_requests方法它默认的回调函数就是parse

2. 建立项目

  咱们的目标是爬取亚马逊商城iphoex的名称,价格以及配送方,注意的是:咱们须要的这些信息都在手机详情页面,而在手机列表页面只有咱们点击它的图片或者文字才会看到手机详细信息

  想要获取手机的信息,如今咱们须要进行分布爬取,第一次先获取每个手机详情页面的url,能够经过手机列表页面的图片进行获取,也能经过文字获取,而后经过二次解析去拿到咱们须要的信息

  拿到信息后,经过MongoDB对咱们的信息作持久化处理

2.1 建立项目文件

爬虫Scrapy命令:
    1 scrapy startproject Amazon         # 建立爬虫项目
    2 scrapy genspider amazon amazon.cn      # 生成一个爬虫程序
    3 scrapy crawl amazon                   # 运行scrapy项目

建立好后 spiders下自动生成的amazon.py文件

# -*- coding: utf-8 -*-
import scrapy


class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['amazon.cn']
    start_urls = ['http://amazon.cn/']

    def parse(self, response):
        pass
View Code

2.2 建立启动文件

  建立好后,它默认只能在终端运行,咱们能够在它的根目录下建立一个bin.py文件,来做为它的执行文件

from scrapy.cmdline import execute

execute(["scrapy","crawl","amazon",'--nolog'])  # 不打印日志信息

  若是不须要相关日志信息,,能够在列表后面追加一个参数:'--nolog'

2.3 关闭ROBOTSTXT_OBEY命令

  关闭setting下的ROBOTSTXT_OBEY命令,该命令的做用是让你遵循爬虫协议的状况下爬取相关内容,咱们为了不它对咱们爬取时的影响,能够把他修改成False

 2.4 获取商品信息

 获取商品列表的详情连接

amazon.py

import scrapy
from scrapy import Request  # 导入模块

class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['amazon.cn']
    # 自定义配置,在Spider中custom_settings设置的是None
    custom_settings = {
        'REQUEST_HEADERS': {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
        }
    }

    def start_requests(self):
        r1 = Request(
                    url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex",
                     headers=self.settings.get('REQUEST_HEADERS'),
                     )
        yield r1

    def parse(self, response):
        # 获取商品名
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract()
        # 商品单个商品详情连接
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract()
        # 获取整个页面商品详情连接
        # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
        detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract()

        print(detail_urls)

如今让scrapy去访问这些连接,只要parse函数返回一个Request对象,它就会放到异步请求列表里面,并由twisted发送异步请求

import scrapy
from scrapy import Request  # 导入模块


class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['amazon.cn']
    # 自定义配置,在Spider中custom_settings设置的是None
    custom_settings = {
        'REQUEST_HEADERS': {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
        }
    }

    def start_requests(self):
        r1 = Request(
            url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex",
            headers=self.settings.get('REQUEST_HEADERS'),
        )
        yield r1

    def parse(self, response):
        # 获取商品名
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract()
        # 商品单个商品详情连接
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract()
        # 获取整个页面商品详情连接
        # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
        detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract()

        for url in detail_urls:
            yield Request(url=url,
                          headers=self.settings.get('REQUEST_HEADERS'),  # 请求头
                          callback=self.parse_detail,  # 回调函数
                          dont_filter=True  # 不去重
                          )

    def parse_detail(self, response):  # 获取商品详细信息
        # 商品名,获取第一个结果
        name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
        if name:
            name = name.strip()
        # 商品价格
        price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
        # 配送方式
        delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first()

        print(name, price, delivery)

2.5 存储商品信息到MongoDB

  咱们须要使用items.py文件

import scrapy

# 获取你想要的字段
class AmazonItem(scrapy.Item):
    # define the fields for your item here 
    name = scrapy.Field()
    price= scrapy.Field()
    delivery=scrapy.Field()

  并修改amazon.py最后的数据返回值

amazon.py

import scrapy
from scrapy import Request  # 导入模块
from Amazon.items import AmazonItem

class AmazonSpider(scrapy.Spider):
    name = 'amazon'
    allowed_domains = ['amazon.cn']
    # 自定义配置,在Spider中custom_settings设置的是None
    custom_settings = {
        'REQUEST_HEADERS': {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',
        }
    }

    def start_requests(self):
        r1 = Request(
            url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex",
            headers=self.settings.get('REQUEST_HEADERS'),
        )
        yield r1

    def parse(self, response):
        # 获取商品名
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract()
        # 商品单个商品详情连接
        # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract()
        # 获取整个页面商品详情连接
        # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract()
        detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract()

        for url in detail_urls:
            yield Request(url=url,
                          headers=self.settings.get('REQUEST_HEADERS'),  # 请求头
                          callback=self.parse_detail,  # 回调函数
                          dont_filter=True  # 不去重
                          )

    def parse_detail(self, response):  # 获取商品详细信息
        # 商品名,获取第一个结果
        name = response.xpath('//*[@id="productTitle"]/text()').extract_first()
        if name:
            name = name.strip()
        # 商品价格
        price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first()
        # 配送方式
        delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first()

        # 生成标准化数据

        item = AmazonItem()  # 实例化
        # 增长键值对
        item["name"] = name
        item["price"] = price
        item["delivery"] = delivery

        return item     # 返回的是一个字典

  处理Spider中获取到的Item ,须要PipeLine将数据储存到MongoDB中

pipelines.py

from pymongo import MongoClient

class MongodbPipeline(object):

    def __init__(self, host, port, db, table):
        self.host = host
        self.port = port
        self.db = db
        self.table = table

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先经过getattr判断咱们是否自定义了from_crawler,有则调它来完
        成实例化
        """
        HOST = crawler.settings.get('HOST')
        PORT = crawler.settings.get('PORT')
        DB = crawler.settings.get('DB')
        TABLE = crawler.settings.get('TABLE')
        return cls(HOST, PORT, DB, TABLE)

    def open_spider(self, spider):
        """
        爬虫刚启动时执行一次
        """
        # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port))
        self.client = MongoClient(host=self.host, port=self.port)

    def close_spider(self, spider):
        """
        爬虫关闭时执行一次
        """
        self.client.close()

    def process_item(self, item, spider):
        # 操做并进行持久化
        d = dict(item)
        if all(d.values()):
            self.client[self.db][self.table].insert(d)
            print("添加成功一条")

  修改settings.py,在下面增长MongoDB链接信息

# MongoDB链接信息
HOST="127.0.0.1"
PORT=27017
DB="amazon"  # 数据库名
TABLE="goods"  # 表名

  同时开启MongoDB的PipeLine信息,注意这里开启后还须要进行修改,咱们pipelines下的名称是MongodbPipeline

ITEM_PIPELINES = {
   'Amazon.pipelines.MongodbPipeline': 300,
}

此时在cmd下启动咱们的mongodb(mongod),进入咱们的数据库(mongo),并自行建立数据库

执行咱们的bin文件,此时会在你会发现数据都存储进咱们的数据库内

2.6 存储商品信息到本地

  咱们只须要在pipelines.py里面再添加一个相关的类便可

class FilePipeline(object):

    def __init__(self, file_path):
        self.file_path=file_path

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先经过getattr判断咱们是否自定义了from_crawler,有则调它来完
        成实例化
        """
        file_path = crawler.settings.get('FILE_PATH')
        return cls(file_path)


    def open_spider(self, spider):
        """
        爬虫刚启动时执行一次
        """
        print('==============>爬虫程序刚刚启动')
        self.fileobj=open(self.file_path,'w',encoding='utf-8')

    def close_spider(self, spider):
        """
        爬虫关闭时执行一次
        """
        print('==============>爬虫程序运行完毕')
        self.fileobj.close()

    def process_item(self, item, spider):


        # 操做并进行持久化
        print("items----->",item)
        # return表示会被后续的pipeline继续处理
        d = dict(item)
        if all(d.values()):

            self.fileobj.write("%s\n" %str(d))

        return item

        # 表示将item丢弃,不会被后续pipeline处理
        # raise DropItem()

  而后再在setting配置文件中配置ITEM_PIPELINES信息以及FILE_PATH信息便可

ITEM_PIPELINES = {
   'DianShang.pipelines.MongodbPipeline': 300,  # 优先级300
   'DianShang.pipelines.FilePipeline': 500,     # 优先级500先执行
}

FILE_PATH="pipe.txt"    # 文件名称

 2.7 利用代理池进行目标爬取

   这里主要是考虑目标网站检测咱们ip问题,通常咱们须要大量爬取相关信息,须要使用代理池(在github上搜索下载),每次更换咱们的ip地址,下载后放入本地,根据readme文件进行相关配置

  这里还须要使用中间件。从咱们开始的那张图能够看到咱们整个流程两个地方具备中间件:爬取内容的时候通过SpiderMidderware,存储的时候通过DownerMidderware,须要进行以下操做:

#一、与middlewares.py同级目录下新建proxy_handle.py
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").text

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
    
    

#二、middlewares.py
from Amazon.proxy_handle import get_proxy,delete_proxy

class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求须要被下载时,通过全部下载器中间件的process_request调用
        :param request:
        :param spider:
        :return:
            None,继续后续中间件去下载;
            Response对象,中止process_request的执行,开始执行process_response
            Request对象,中止中间件的执行,将Request从新调度器
            raise IgnoreRequest异常,中止process_request的执行,开始执行process_exception
        """
        proxy="http://" + get_proxy()
        request.meta['download_timeout']=20
        request.meta["proxy"] = proxy
        print('为%s 添加代理%s ' % (request.url, proxy),end='')
        print('元数据为',request.meta)

    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('返回状态吗',response.status)
        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将会被从新调用下载
        """
        print('代理%s,访问%s出现异常:%s' %(request.meta['proxy'],request.url,exception))
        import time
        time.sleep(5)
        delete_proxy(request.meta['proxy'].split("//")[-1])
        request.meta['proxy']='http://'+get_proxy()

        return request

具体操做能够参考博客

项目最终骨架:

相关文章
相关标签/搜索