目录

一、Scrapy框架

  • (1)安装
  • (2)运行流程
  • (3)解析介绍xpath/css
  • 二、scrapy genspider生成爬虫脚本代码并完整的写一个demo代码
  • (1)创建项目和默认爬虫脚本代码
  • A:终端创建项目和爬虫,输入如下命令
  • B:生成的项目目录结构:
  • C:qsbk.py爬虫脚本代码各个语句含义:
  • (2)制作爬虫开始爬取网页
  • A、settings设置
  • B、items.py里面编写需要的字段模型
  • C、qsbk.py里面编写爬虫
  • (3)存储内容 (pipelines.py):设计管道存储爬取内容
  • A、settings.py设置ITEM_PIPELINES打开权限
  • B、编写pipelines.py
  • C、不通过pipelines,直接命令保存到文件
  • D、MongoDB存储
  • E、MySQL存储
  • (4)运行爬虫的两种方法
  • (5)scrapy运行爬虫的不输出日志
  • (6)scrapy翻页跑代码以及不同页面数据合并的传参方式
  • (7)scrapy代码运行后日志与配置信息简要介绍
  • 三、scrapy Shell操作
  • 四、scrapy Request对象/Respose对象
  • (1)Spider
  • (2)Request对象
  • (3)Respose对象
  • 五、scrapy内置下载图片
  • (1)介绍
  • (2)设置
  • 六、scrapy下载器中间件Downloader Middlewares
  • (1)下载请求头中间件
  • (2)ip代理中间件
  • (3)selenium中间件
  • 七、scrapy genspider -t 操作步骤CrawlSpider,适用于数据量大的网站
  • (1)创建项目和爬虫
  • A:终端创建项目和爬虫,输入如下命令
  • B:生成的项目目录结构:
  • (2)制作爬虫开始爬取网页
  • A、settings设置HEADERS等内容
  • B、items.py里面编写需要的字段模型
  • C、wxap_spider.py里面编写爬虫
  • (3)存储内容 (pipelines.py):设计管道存储爬取内容
  • A、settings设置ITEM_PIPELINES
  • B、编写pipelines.py
  • (4)运行爬虫
  • 八、scrapy-Redis分布式爬虫
  • 九、Scrapyd部署
  • 十、Gerapy爬虫管理框架使用

一、Scrapy框架

  • 其中目录
(1)安装
  • 安装Scrapy:pip install scrpay
  • Scrapy是一个适用爬取网站数据、提取结构性数据的应用程序框架
(2)运行流程

python django 正式项目源码 python项目代码_ide

  • 中间引擎的作用:引擎接收spider产生的request对象(给调度器,也向调度器索取分配);引擎接收downloader传来的response对象(将response对象传给spider)
  • spider的作用:每个函数产生独立的新的request对象(给引擎放到调度器里面,给引擎从调度器里面取给下载器);也接收独立的response对象(下载器会把response对象给引擎,引擎会把response对象给spider);spider收到response对象后,要么生成字典给引擎发给pipeline处理,要么生成新的request对象给引擎给调度器等待调度
  • 下载器的作用:接收request对象,得到对应的response对象,发给引擎,引擎再发给spider
  • pipeline的作用:接收item字典(引擎从spider里面拿到的)进行存储处理
  • ① 启动爬虫项目时,引擎根据爬取的目标站点找到处理该站点的Spider,Spider会根据最初的需要爬取的页面生成一个或多个Request对象,发给引擎
  • ② 引擎从Spider获取Request对象,发给调度器里面等待调度
  • ③ 引擎从调度器里面抽取合适的Request对象,然后转发给下载器,转发过程会经历一些中间件(从左到右)
  • ④ 引擎从下载器里面拿到Response对象,然后转发给Spider,转发过程会经历一些中间件(从右到左)
  • ⑤ Spider拿到Response对象处理,生成item字典或者新的Request对象给引擎
  • ⑥ 引擎将Spider发来的一个或者多个item字典转发给pipelines进行处理,将一个或者多个Request对象转发给调度器等待下一次调度
  • ⑦ 重复步骤②~⑥,直到调度器中没有更多的Request对象,这时候引擎会关闭Spider,爬取过程结束
(3)解析介绍xpath/css
  • scrapy提供了两个方法,response.xpath()和response.css()
  • getall()匹配所有元素,extract_first()匹配单个元素
  • get()传递一个默认值参数,可以在xpath匹配不到结果的时候,返回这个参数代替;
  • 正则匹配:re()匹配所有结果,re_first()匹配第一个
  • response.xpath()使用:
response.xpath("//a/text()").getall()
response.xpath("//a/text()").get()
response.xpath("//a/text()").get("Default None")
response.xpath("//a/text()").re('Name:\s(.*)')
response.xpath("//a/text()").re_first('Name:\s(.*)')
  • response.css()使用:
response.css("a[href='image.html']::text").getall()
response.css("a[href='image.html']::text").get()
response.css("a[href='image.html'] img::attr(src)").get("Default None")

二、scrapy genspider生成爬虫脚本代码并完整的写一个demo代码

(1)创建项目和默认爬虫脚本代码
  • 以下ABC的目的就是建了一个爬虫脚本qsbk.py,然后介绍了下目录结构,然后再介绍了爬虫脚本里面各个生成的代码含义而已
A:终端创建项目和爬虫,输入如下命令
  • 创建一个scrapy项目: 主要经历以下三个步骤,即可创建一个scrapy项目
scrapy startproject [项目名称]
cd [项目名称路径]
scrapy genspider [爬虫文件名] [爬虫URL域名]
B:生成的项目目录结构:
  • 目录结构介绍了解
  • spiders:以后所有的爬虫,都是存放到这个里面
  • items.py:用来存放爬虫爬取下来数据的模型;
  • middlewares.py:用来存放各种中间件的文件;
  • pipelines.py:定义数据管道,用来将items的模型存储到本地磁盘中;
  • settings.py:本爬虫的一些配置信息(比如请求头、多久发送一次请求、ip代理池等);
  • scrapy.cfg:它是scrapy项目的配置文件,其内定义了项目的配置文件路径,部署相关信息等内容
C:qsbk.py爬虫脚本代码各个语句含义:
  • name:它是每个项目唯一的名字,用来区分不同的Spider
  • allowed_domains:它是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉
  • start_urls:它包含了Spider在启动时爬取的url列表,初始请求的url时由它定义的
  • parse:该方法负责解析返回的响应、提取数据或者进一步生成处理的请求
  • response是一个’scrapy.http.response.html.HtmlResponse’对象,可以执行‘xpath’和’css’语法来提取数据,提取出来的数据是一个‘Selector’或者是一个’SelectorList’对象,如果想要获取其中的字符串,可以用getall或者get方法
# -*- coding: utf-8 -*-
import scrapy


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'
    allowed_domains = ['qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/page/1/']

    def parse(self, response):
        pass
(2)制作爬虫开始爬取网页
  • 爬虫要想在scrapy里面正常的运行,首先得把settings.py里面的一些配置权限给打开,所以一般会首先改的配置如下; items主要是固定表结构的作用,就是每次固定存储的字段,不管该字段是否有值
A、settings设置
  • ROBOTSTXT_OBEY改为False ,DEFAULT_REQUEST_HEADERS修改
# Obey robots.txt rules
ROBOTSTXT_OBEY = False   # robots协议遵守
CONCURRENT_REQUESTS = 10   # 并发量
RETRY_HTTP_CODES = [403, 500, 502, 503, 504]  # 需要重试的状态码,如果不进行这样的设置,该请求失败了则会被丢弃
HTTPERROR_ALLOWED_CODES = [202, 400, 412]  # 允许的错误状态码,进行进一步处理
DOWNLOAD_TIMEOUT = 10  # 超时时间
RETRY_TIMES = 5  # 重试次数
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/79.0.3945.88 Safari/537.36'
}
B、items.py里面编写需要的字段模型
  • 定义字段模型,需要的字段
import scrapy


class HelloItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    author = scrapy.Field()
    content = scrapy.Field()
C、qsbk.py里面编写爬虫
  • 修改start_urls,在qsbk.py里面的parse方法里编写解析过程
  • 定位下一页,回调函数,需要用到scrapy.Request,需要传递两个参数:url和callback
  • dont_filter=False过滤掉已请求过的url;反之不用过滤
import scrapy
from Hello.items import HelloItem


class QsbkSpider(scrapy.Spider):
    name = 'qsbk'
    allowed_domains = ['qiushibaike.com']
    start_urls = ['https://www.qiushibaike.com/text/page/1/']
    base_domain = "https://www.qiushibaike.com"

    def parse(self, response):
        divs = response.xpath('//div[@class="col1 old-style-col1"]/div')
        for div in divs:
            author = div.xpath(".//h2/text()").get().strip()
            # 选择器的前方加.,代表提取元素内部的数据,如果没有加.则代表从根节点开始提取
            content = div.xpath('.//div[@class="content"]//text()').getall()
            content = ''.join(content).strip()
            # items.py里面定义好模型后,替换下面两行
            # duanzi = {'author': author, 'content': content}
            # yield duanzi
            item = HelloItem(author=author, content=content)
            yield item
            
        # 处理下一页的请求,并settings设置DOWNLOAD_DELAY = 1
        next_page_url = response.xpath('//span[@class="next"]/ancestor::a/@href').get()
        if not next_page_url:
            return
        else:
            yield scrapy.Request( f"{self.base_domain}{next_page_url}", callback=self.parse, dont_filter=False)
(3)存储内容 (pipelines.py):设计管道存储爬取内容
A、settings.py设置ITEM_PIPELINES打开权限
  • ITEM_PIPELINES设置,其中值越小优先级越高
ITEM_PIPELINES = {
   'Hello.pipelines.HelloPipeline': 300,
}
B、编写pipelines.py
  • open_spider()方法,当爬虫被打开的时候执行
  • process_item()方法,当爬虫有item传过来的时候会被调用
  • close_spider()方法,当爬虫关闭时会被调用
import json


class HelloPipeline(object):

    def open_spider(self, spdier):
        print("爬虫开始了……")

    def process_item(self, item, spider):
        with open('duanzi.json', "a+", encoding="utf-8") as fp:
            json.dump(dict(item), fp, indent=4, ensure_ascii=False)
            return item

    def close_spider(self, spdier):
        print("爬虫结束了……")
  • 为防止pipeline被所有的spider都运行,可通过如下方式进行过滤设置 ,当然也可以在spider文件里面自定义custom_settings覆盖setting.py里面的通用pipeline开启设置,
  • 上述代码的第二种写法:使用JsonLinesItemExporter:这个每次调用export_item的时候就把这个item存储到硬盘中,输出也是字典
from scrapy.exporters import JsonLinesItemExporter


class HelloPipeline(object):

    def __init__(self):
        self.fp = open('duanzi.json', 'wb')
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spdier):
        print("爬虫开始了……")

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

    def close_spider(self, spdier):
        self.fp.close()
        print("爬虫结束了……")
C、不通过pipelines,直接命令保存到文件

命令

解释

scrapy crawl quotes -o quotes.json

保存成JSON文件

scrapy crawl quotes -o quotes.jl

每一个Item输出一行JSON

scrapy crawl quotes -o quotes.csv

保存成csv文件

scrapy crawl quotes -o quotes.xml

保存成xml文件

scrapy crawl quotes -o quotes.pickle

保存成pickle文件

scrapy crawl quotes -o quotes.marshal

保存成marshal文件

scrapy crawl quotes -o quotes.ftp://user:pass@ftp.example.com/path/to/quotes.csv

ftp远程输出

D、MongoDB存储
import pymongo


class MongoPipeline(object):

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
	
	@classmethod
	def from_crawler(cls, crawler):
		return cls(
			mongo_uri = crawler.settings.get('MONGO_URI')
			mongo_db = crawler.settings.get('MONGO_DB')
		)
		
    def open_spider(self, spdier):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def process_item(self, item, spider):
        self.db[item.collection].insert(dict(item))
        return item

    def close_spider(self, spdier):
        self.client.close()
E、MySQL存储
import pymysql


class MysqlPipeline(object):

    def __init__(self, host, database, user, password, port):
        self.host = host
        self.database = database
        self.password = password
        self.port = port
	
	@classmethod
	def from_crawler(cls, crawler):
		return cls(
			host = crawler.settings.get('MYSQL_HOST')
			database = crawler.settings.get('MYSQL_DATABASE')
			user = crawler.settings.get('MYSQL_USER')
			password = crawler.settings.get('MYSQL_PASSWORD')
			port = crawler.settings.get('MYSQL_PORT')
		)
		
    def open_spider(self, spdier):
        self.db = pymysql.connect(self.host, self.user, self.password, self.database, charset="utf-8", port=self.port)

    def process_item(self, item, spider):
        data = dict(item)
        keys = ','.join(data.keys())
        values = ','.join(['%s'] * len(data))
		sql = 'insert into %s (%s) values (%s)' % (item.table, keys, values)
		self.cursor.execute(sql, tuple(data.values()))
		self.db.commit()
        return item

    def close_spider(self, spdier):
        self.db.close()
(4)运行爬虫的两种方法
  • 第一种:切换到Hello文件夹路径下,然后直接终端scrapy crawl qsbk
E:\Test_com\Hello>scrapy crawl qsbk
  • 第二种:在Hello文件夹下新建一个start.py文件,然后写命令,然后运行start.py文件即可
from scrapy import cmdline

cmdline.execute(['scrapy', 'crawl', 'qsbk'])
(5)scrapy运行爬虫的不输出日志
  • scrapy crawl qsbk --nolog
(6)scrapy翻页跑代码以及不同页面数据合并的传参方式
  • 判断回调函数终止条件(或者搜索式应该也可以),通过meta作为中间人合并不同页面数据
(7)scrapy代码运行后日志与配置信息简要介绍
  • 日志视频教程
  • 配置视频教程

三、scrapy Shell操作

  • 可以方便我们做一些数据提取的测试代码;
  • 如果想要执行scrapy命令,首先进入到scrapy所在的环境中;
scrapy shell url
# scrapy shell https://doc.scrapy.org/en/latest/_static/selectors-sample1.html

四、scrapy Request对象/Respose对象

(1)Spider
  • 在回调方法中,返回结果可以有两种形式:① 即将解析的字典或Item对象 ② 即将解析的下一个链接,并以此链接为请求解析响应的方法

python django 正式项目源码 python项目代码_ide_02

  • custom_settings : spider里面设置可以覆盖外围的通用setting.py里面的设置,这样一个爬虫一个独立的设置

python django 正式项目源码 python项目代码_学习_03

(2)Request对象
  • callback:在下载其下载完响应的数据后执行的回调函数;
  • headers:请求头,对于一些固定的设置,放在settings.py中指定就可以了,对于那些非固定的,可以在发送请求时指定
  • meta:共享数据,用于在不同的请求之间传递数据的
  • dot_filter:scrapy默认(False)会对Request类对象去重,不想去重得改成True,表示不由调度器过滤,在执行多次重复的请求的时候用的比较多
  • priority:Request对象执行优先级,默认是0,数值越打,就越被有限调度并执行
  • errback:在发生错误的时候执行的函数
  • cookies: 携带的cookie,可以是字典形式
  • request.meta['proxy']可以用来设置请求的代理,request.meta['max_retry_times']可以用来设置请求的最大重试次数
import scrapy
scrapy.Request(url, callback=None, method='GET', headers=None, body=None,cookies=None, meta=None, 
encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None, cb_kwargs=None)

python django 正式项目源码 python项目代码_ide_04

  • 发送GET请求:进行页码循环,以及多个yield分别存储文件,以及传Request对象
import re

import scrapy
from ..items import NmpaicItem


class NmpaicorgSpider(scrapy.Spider):
    name = 'nmpaicorg'
    allowed_domains = ['www.nmpaic.org.cn']
    start_urls = ['https://www.nmpaic.org.cn/zcfgjjd/index.html']

    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Host": "www.nmpaic.org.cn",
        "Referer": "https://www.nmpaic.org.cn/zcfgjjd/index_1.html",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
    }
    page_num = 0
    max_page = 0

    # def start_requests(self):
    #     url = 'https://www.nmpaic.org.cn/zcfgjjd/index.html'
    #     yield scrapy.Request(url, headers=self.headers)

    def parse(self, response, **kwargs):
        for tag_ in response.css(".rightBox li"):
            detail_url = f"https://www.nmpaic.org.cn/zcfgjjd{tag_.css('a').attrib['href'][1:]}"
            title = tag_.css("a::text").get()
            pub_time = tag_.css("span::text")[0].root.lstrip("时间:")
            # 存一份列表页的文件
            yield {"page_num": self.page_num+1, "title": title, "pub_time": pub_time, "detail_url": detail_url}
            # 请求详情页
            yield scrapy.Request(detail_url, headers=self.headers, callback=self.parse_detail)
        # 页码循环
        if not self.max_page:
            self.max_page = int(re.search(r"countPage = (\d+)", response.text).group(1))
        self.page_num += 1
        if self.page_num < self.max_page:
            next_page = f"https://www.nmpaic.org.cn/zcfgjjd/index_{self.page_num}.html"
            yield scrapy.Request(next_page, headers=self.headers, callback=self.parse)

    def parse_detail(self, response):
        item = NmpaicItem()
        item['title'] = response.css(".contitle h2::text").extract_first()
        item['source'] = response.css("meta[name='ContentSource']").attrib['content']
        item['pub_date'] = response.css(".lytlesj span::text").get().lstrip("时间:").strip()
        item['text'] = "\n\n".join([t.root.strip() for t in response.css('.congczt').css("::text")])
        item['url'] = response.url
        yield item
  • 发送POST请求:有时候我们想要在请求数据的时候发送post请求;那么这时候需要使用Request的子类scrapy.FormRequest(url, formdata=data, callback=self.parse_page) 或者scrapy.JsonRequest(url, data=data, callback=self.parse_page)来实现;如果想要在爬虫一开始的时候就发送POST请求,那么需要在爬虫类中重写start_requests(self)方法,并且不再调用start_urls里的url,在这个方法中发送post请求
# 模拟登录人人网, 只是案例,网站已改,不一定能实现
import scrapy


class RenrenSpiderSpider(scrapy.Spider):
   name = 'renren_spider'
   allowed_domains = ['renren.com']
   start_urls = ['http://renren.com/']

   # def parse(self, response):
   #     pass
   def start_requests(self):
       url = "http://www.renren.com/PLogin.do"
       data = {"email": "970138074@qq.com", "password": "pythonspider"}
       request = scrapy.FormRequest(url, formdata=data, callback=self.parse_page)
       yield request

   def parse_page(self, response):
       # with open("renren.html", "w", encoding="utf-8") as f:
       #     f.write(response.text)
       request = scrapy.Request(url="http://www.renren.com/880151247/profile", callback=self.parse_profile)
       yield request

   def parse_profile(self, response):
       with open("renren.html", "w", encoding="utf-8") as f:
           f.write(response.text)
  • 第二个post请求案例
# 模拟登录豆瓣网,可能已失效,验证码已改,只是一个思路案例
import scrapy
from urllib import request
from PIL import Image


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


    def parse(self, response):
        form_data = {
            "source": "None",
            "redir": "https://www.douban.com/",
            "form_email": "970138074@qq.com",
            "form_password": "pythonspider",
            "login": "登录"
        }
        captcha_url = response.css("img#captcha_image::attr(src)").get()
        if captcha_url:
            captcha = self.regonize_captcha(captcha_url)
            form_data['captcha-solution'] = captcha
            captcha_id = response.xpath("//input[@name='captcha-id']/@value").get()
            form_data['captcha-id'] = captcha_id
        yield scrapy.FormRequest(url=self.login_url, formdata=form_data, callback=self.parse_after_login)

    def parse_after_login(self, response):
        if response.url == "https://www.douban.com/":
            print("登录成功")
        else:
            print("登录失败")

    @staticmethod
    def regonize_captcha(image_url):
        """肉眼识别验证码,以前网站是验证码,现在是滑动,只提供简短思路"""
        request.urlretrieve(image_url, 'captcha.png')
        image = Image.open('captcha.png')
        image.show()
        captcha = input("请输入验证码:")
        return captcha
  • 第三个post请求案例,如果媒体类型是'Content-Type': "application/json",则post请求传送方式传参是body则如下
import scrapy
import json
import logging
from ..items import SmjjItem


class GsamacSpider(scrapy.Spider):
    name = 'gsamac'
    allowed_domains = ['gs.amac.org.cn']
    start_urls = ['http://gs.amac.org.cn/']
    headers = {
        'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9",
        'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0",
        'Host': 'gs.amac.org.cn',
        'Referer': "https://gs.amac.org.cn/amac-infodisc/res/cancelled/manager/index.html",
        'Content-Type': "application/json",
        "X-Requested-With": "XMLHttpRequest"
    }

    def start_requests(self):
        url = "https://gs.amac.org.cn/amac-infodisc/api/cancelled/manager?rand=0.6697093190092722&page=0&size=20"
        data = "{}"
        yield scrapy.FormRequest(url, method='POST', body=data, headers=self.headers, callback=self.parse_page)

    def parse_page(self, response):
        total_page = response.json()['totalPages']
        for page in range(total_page-1, -1, -1):
            logging.info(f"Page_Crawl Now {page+1}/{total_page}")
            url = f"https://gs.amac.org.cn/amac-infodisc/api/cancelled/manager?rand=0.6697093190092722&page={page}&size=20"
            yield scrapy.Request(url, method='POST', body="{}", headers=self.headers, callback=self.parse_page_2)

    def parse_page_2(self, response):
        for data in response.json()["content"][::-1]:
            print(data)
            item = SmjjItem()
            item['name'] = data.get("orgName", "")
            item["credit_no"] = data.get("orgCode", "")
            yield item
(3)Respose对象
  • meta:从其他请求传过来的meta属性,可以用来保持多个请求之间的数据连接;
  • encoding:返回当前字符串编码和解码的格式;
  • text:将返回来的数据作为unicode字符串返回;
  • body:将返回来的数据作为bytes字符串返回;
  • xpath:xpath选择器;
  • css:css选择器
  • ip_address: 服务器的ip地址

五、scrapy内置下载图片

(1)介绍
  • scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片提供了一个可重用的items pipelines。这写pipelines有些共同的方法和结构我们称之为media pipeline),一般来说会使用Files Pipeline或者Images Pipeline;
  • 使用scrapy内置下载文件的优点:避免重复下载,方便指定路径,方便转换为通用格式,方便生成缩略图,方便检测图片宽和高,异步下载,效率非常高
  • 步骤:
  • 定义好一个item,然后item中定义两个属性,分别为image_urls以及images;image_urls是用来存储需要下载的文件的url链接,需要给一个列表;
  • 当文件下载完成后,会把文件下载的相关信息存储到item的images属性中,比如下载路径、下载的url和文件的校验码等;
  • 在配置文件settings.py中配置IMAGES_STORE,这个配置是用来设置文件下载下来的路径;
  • 启动pipeline:在ITEM_PIPELINES中设置’scrapy.pipelines.images.ImagesPipelin’: 1
(2)设置
  • items.py设置
import scrapy


class ImageItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    category = scrapy.Field()
    image_urls = scrapy.Field()
    images = scrapy.Field()
  • settings设置
import os
# 图片pipelines设置,将原有的pipeline注释掉
ITEM_PIPELINES = {
   # 'bmw.pipelines.BmwPipeline': 300,
    'scrapy.pipelines.images.ImagesPipelin': 1
}
# 图片下载路径, 供images pipelines使用
IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')

六、scrapy下载器中间件Downloader Middlewares

  • 下载器中间件是引擎和下载器之间通信的中间件。在这个中间件中我们可以设置代理、更换请求头等来达到反反爬虫的目的;要写下载器中间件,可以在啊下载器中实现两个方法。
  • 一个是process_request(self, request, spider),这个方法是在请求发送之前会执行,
  • 还有一个是process_response(self,request,response,spider),这个方法是数据下载到引擎之前执行;
  • 记得最开始的那张图,从左到右,即引擎道下载器,数字越小越靠近引擎(process_request先调用),数字越大越靠近下载器(process_response先调用)
(1)下载请求头中间件
  • user-agent链接
  • settings设置:
DOWNLOADER_MIDDLEWARES = {
   'Hello.middlewares.UserAgentDownloadMiddleware': 543,
}
  • middlewares.py
import random


class UserAgentDownloadMiddleware:
    USER_AGENT = [
        "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
    ]
    
    def process_request(self, request, spider):
        user_agent = random.choice(self.USER_AGENT)
        request.headers['User-Agent'] = user_agent
(2)ip代理中间件
  • 查看当前ip代理链接
  • settings设置
DOWNLOADER_MIDDLEWARES = {
    'Hello.middlewares.UserAgentDownloadMiddleware': 543,
    'Hello.middlewares.IPProxyDownloadMiddleware': 100,
}
class IPProxyDownloadMiddleware:
    PROXIES = ["http://101.132.190.101:80", "http://175.148.71.139:1133", "http://222.95.144.68:3000"]

    def process_request(self, request, spider):
        proxy = random.choice(self.PROXIES)
        request.meta['proxy'] = proxy
(3)selenium中间件
  • 当process_request()方法返回Response对象的时候,更低优先级的Downloader Middleware的process_request()和process_exception()方法就不会被继续调用了, 转而开始执行每个Downloader Middleware的process_response()方法,调用完毕之后直接将Response对象发送给Spider处理
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from scrapy.http import HtmlResponse
from logging import getLogger


class SeleniumMiddleware():
    def __init__(self, timeout=None, service_args=[]):
        self.logger = getLogger(__name__)
        self.timeout = timeout
        self.browser = webdriver.PhantomJS(service_args=service_args)
        self.browser.set_window_size(1400, 700)
        self.browser.set_page_load_timeout(self.timeout)
        self.wait = WebDriverWait(self.browser, self.timeout)
    
    def __del__(self):
        self.browser.close()
    
    def process_request(self, request, spider):
        """
        用PhantomJS抓取页面
        :param request: Request对象
        :param spider: Spider对象
        :return: HtmlResponse
        """
        self.logger.debug('PhantomJS is Starting')
        page = request.meta.get('page', 1)
        try:
            self.browser.get(request.url)
            if page > 1:
                input = self.wait.until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
                submit = self.wait.until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
                input.clear()
                input.send_keys(page)
                submit.click()
            self.wait.until(
                EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
            self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
            return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8',
                                status=200)
        except TimeoutException:
            return HtmlResponse(url=request.url, status=500, request=request)
    
    @classmethod
    def from_crawler(cls, crawler):
        return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'),
                   service_args=crawler.settings.get('PHANTOMJS_SERVICE_ARGS'))

七、scrapy genspider -t 操作步骤CrawlSpider,适用于数据量大的网站

(1)创建项目和爬虫
A:终端创建项目和爬虫,输入如下命令
  • 数据量大的网站
scrapy startproject [项目名称]
cd [项目名称路径]
scrapy genspider -t crawl [爬虫文件名] [爬虫URL域名]
B:生成的项目目录结构:

python django 正式项目源码 python项目代码_python_05

(2)制作爬虫开始爬取网页
A、settings设置HEADERS等内容
  • ROBOTSTXT_OBEY改为False
# Obey robots.txt rules
ROBOTSTXT_OBEY = False   # robots协议遵守
CONCURRENT_REQUESTS = 10   # 并发量
RETRY_HTTP_CODES = [403, 500, 502, 503, 504]  # 需要重试的状态码,如果不进行这样的设置,该请求失败了则会被丢弃
HTTPERROR_ALLOWED_CODES = [202, 400, 412]  # 允许的错误状态码,进行进一步处理
DOWNLOAD_TIMEOUT = 10  # 超时时间
RETRY_TIMES = 5  # 重试次数
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/79.0.3945.88 Safari/537.36'
}
  • 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/79.0.3945.88 Safari/537.36'
}
B、items.py里面编写需要的字段模型
  • 定义字段模型,需要的字段
import scrapy


class WxappItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    author = scrapy.Field()
    pub_time = scrapy.Field()
C、wxap_spider.py里面编写爬虫
  • 修改start_urls,在wxap_spider.py里面的parse_detail方法里编写解析过程;
  • 需要使用"LinuxExtractor和Rule",这两个东西决定爬虫的具体走向;
  • allow设置的规则的方法,要能够限制在我们想要的url上面,不要和其他的url产生相同的正则表达式即可;
  • 上面情况下使用follow:如果在爬取页面的时候,需要将满足当前条件的url再进行跟进,那么设置为True,否则设置为False;
  • 什么情况下该指定callback:如果这个url对应的页面,只是为了获取更多的url,并不需要里面的数据,可以不指定callback;如果想要获取url对应页面中的数据,那么就需要指定一个callback
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from wxapp.items import WxappItem


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

    rules = (
    # 列表页
        Rule(LinkExtractor(
            allow=r'.+mod=list&catid=2&page=\d'),
            follow=True),
   # 详情页
        Rule(LinkExtractor(
            allow=r'.+article.+\.html'),
            callback="parse_detail",
            follow=False),
    )

    def parse_detail(self, response):
        title = response.xpath('//h1[@class="ph"]/text()').get()
        author_p = response.xpath('//p[@class="authors"]')
        author = author_p.xpath(".//a/text()").get()
        pub_time = author_p.xpath(".//span/text()").get()
        print(f"title:{title},author:{author},pub_time:{pub_time}")
        item = WxappItem(title=title, author=author, pub_time=pub_time)
        yield item
(3)存储内容 (pipelines.py):设计管道存储爬取内容
A、settings设置ITEM_PIPELINES
  • ITEM_PIPELINES设置,其中值越小优先级越高
ITEM_PIPELINES = {
   'Hello.pipelines.HelloPipeline': 300,
}
B、编写pipelines.py
  • 使用JsonLinesItemExporter:这个每次调用export_item的时候就把这个item存储到硬盘中,输出也是字典
from scrapy.exporters import JsonLinesItemExporter


class WxappPipeline(object):
    def __init__(self):
        self.fp = open('wxjc.json', 'wb')
        self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

    def open_spider(self, spdier):
        print("爬虫开始了……")

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

    def close_spider(self, spdier):
        self.fp.close()
        print("爬虫结束了……")
(4)运行爬虫
  • 在wxapp文件夹下新建一个start.py文件,然后写命令,然后运行start.py文件即可
from scrapy import cmdline

cmdline.execute(['scrapy', 'crawl', 'wxap_spider'.split()])

八、scrapy-Redis分布式爬虫

  • 分布式爬虫:应用场景,数据量要求巨大,数据要求时间比较紧张;普通爬虫实现分布式,实现去重集合与任务队列共享
  • clone https://github.com/rmax/scrapy-redis.git,官网demo
  • 分布式爬虫的编写流程,改写视频如下,流程分为如下过程
  • 第一步修改爬虫文件,将普通的爬虫文件按如下格式修改,视频0分0秒为修改流程
  • 第二步:修改setting.py文件,复制默认的settings文件,将现有的普通爬虫的settings文件覆盖,同时修改example的爬虫名,以及注释掉example的多余的pipeline,以及download_delay等,视频7分32秒为修改流程
  • 第三步运行测试,视频10分40秒为修改流程

九、Scrapyd部署

  • 推荐使用Scrapyd-Client使用,下面的步骤可以先忽略
  • 打开cmd窗口,分别运行下面两条命令
pip install scrapyd
scrapyd
  • 然后打开http://127.0.0.1:6800/
  • 部署视频教程

十、Gerapy爬虫管理框架使用