Scrapy框架(高效爬虫)

news2024/11/19 11:20:51

文章目录

  • 一、环境配置
  • 二、创建项目
  • 三、scrapy数据解析
  • 四、基于终端指令的持久化存储
    • 1、基于终端指令
    • 2、基于管道
    • 3、数据同时保存至本地及数据库
    • 4、基于spider爬取某网站各页面数据
    • 5、爬取本页和详情页信息(请求传参)
    • 6、图片数据爬取ImagesPipeline
  • 五、中间件
    • 1、拦截请求中间件(UA伪装,代理IP)
    • 2、拦截响应中间件(动态加载)
  • 六、CrawlSpider(自动请求全站爬取,全部页面,自动下拉滚轮爬取)
  • 七、分布式爬虫
  • 八、增量式爬虫

Scrapy拥有高性能持久化存储,异步数据下载,高性能数据解析,分布式功能

一、环境配置

环境配置步骤如下(要按步骤来):

pip install wheel
下载twisted:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
安装twisted:pip install Twisted-17.1.0-cp36m-win_amd64.whl   (这个文件的路劲)
pip install pywin32
pip install scrapy
测试:在终端输入scrapy指令,没有报错表示安装成功

二、创建项目

步骤:

1、打开pycharm的terminal
2、scrapy startproject first
3、cd first
4、scrapy genspider main www.xxx.com
5、需要有main.py里面的输出,则修改settings.py里面的ROBOTSTXT_OBEY = True改为False
6、scrapy crawl main
  不需要额外的输出则执行scrapy crawl main --nolog
   或者在settings.py里面添加LOG_LEVEL='ERROR',main.py有错误代码会报错(不添加有错误时则不会报错)(常用)

实际操作如下:

打开pycharm的terminal
执行scrapy startproject first,当前目录下会出现个first项目工程,里面有个spiders文件夹,称为爬虫文件夹,在这里放爬虫源文件
在这里插入图片描述

cd first 进入工程目录
执行scrapy genspider main www.xxx.com,在spiders目录中创建一个名为main的爬虫文件,创建的文件自带部分内容
在这里插入图片描述

执行工程:scrapy crawl main (运行main爬虫文件)
在这里插入图片描述
上面parse里面如果有输出运行时没有输出,则需要将settings.py里面的ROBOTSTXT_OBEY = True改为False
在这里插入图片描述

改成False后,修改main.py文件内容如下,运行就会有输出

import scrapy


class MainSpider(scrapy.Spider):
    # 爬虫文件的名称:爬虫源文件的唯一标识
    name = "main"
    # 允许的域名:用来限定start_urls列表中哪些url可以进行请求发送,一般不用
    # allowed_domains = ["www.xxx.com"]
    # 起始的url列表:该列表中存放的url会被scrapy自动进行请求发送
    start_urls = ["http://www.baidu.com/","https://www.sogou.com"]

    # 用于数据解析:response参数表示的就是请求成功后的响应对象
    def parse(self, response):
        print(response)
        pass

在这里插入图片描述使用指令scrapy crawl main --nolog,输出内容就不会输出多余的内容,只输出打印的内容

在这里插入图片描述

如果里面的代码输入错误,运行加上–nolog后不会有输出,看不出报错
有个好方法,运行不用加–nolog,在settings.py里面添加LOG_LEVEL='ERROR',如果输入错误,就会报ERROR,如果没错,输出内容和上面运行带上–nolog一样,这里就不用加–nolog
在这里插入图片描述
在这里插入图片描述

三、scrapy数据解析

首先创建项目

scrapy startproject first
cd first
scrapy genspider main www.xxx.com

在这里插入图片描述

修改settings.py文件的ROBOTSTXT_OBEY = True改为False,才会有输出,添加LOG_LEVEL='ERROR'
在将注释的USER_AGENT取消注释,在页面中复制该内容到这个变量中
在这里插入图片描述

在这里插入图片描述
修改main.py文件

import scrapy

class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["http://www.baidu.com/"]

    def parse(self, response):
        # 解析数据
        # xpath返回的是列表,列表元素一定是Selector类型的对象
        div_list = response.xpath('//div')
        for div in div_list:
            # extract可以将selector对象中data参数存储的字符串提取出来(可以是列表)
            print(div.extract())

执行scrapy crawl main 就会将解析的数据输出
关于Xpath使用请看:https://blog.csdn.net/weixin_46287157/article/details/116432393

四、基于终端指令的持久化存储

1、基于终端指令

只可以将parse方法的返回值存储到本地的文本文件中
基于上面解析的数据,将要保存的数据保存在datas中,然后return返回
再执行指令scrapy crawl main -o ./a.csv,就可以将返回的datas数据保存在a.csv中(要加-o参数保存文件,也可以./a,txt)
注意:持久化存储的文本类型只可以为json, jsonlines, jsonl, jl, csv, xml, marshal, pickle

import scrapy


class MainSpider(scrapy.Spider):
    name = "main"
    allowed_domains = ["www.baidu.com"]
    start_urls = ["http://www.baidu.com/"]

    def parse(self, response):
        datas = []
        # 解析数据
        # xpath返回的是列表,列表元素一定是Selector类型的对象
        div_list = response.xpath('//div[@id="..."]/div')
        for div in div_list:
            # extract可以将selector对象中data参数存储的字符串提取出来(可以是列表)
            datas.append(div.xpath('./div').extract())
        return datas

2、基于管道

首先需要创建项目:

1、打开pycharm的terminal
2、scrapy startproject first
3、cd first
4、scrapy genspider main www.xxx.com
5、修改settings.py里面的ROBOTSTXT_OBEY = True改为False并添加LOG_LEVEL='ERROR'
6、scrapy crawl main

接着需要在主函数(main.py文件)中进行数据解析,main.py内容如下

import scrapy
from first.items import FirstItem

class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.gushiwen.cn/"]

    def parse(self, response):
        # 解析数据
        # xpath返回的是列表,列表元素一定是Selector类型的对象
        div_list = response.xpath('/html/body/div[2]/div[1]/div')
        for div in div_list:
            # extract可以将selector对象中data参数存储的字符串提取出来(可以是列表)
            # 解析诗歌标题和内容
            # 加个if判断,如果解析到的不为空,就进行存储
            if len(div.xpath('./div[1]/p[1]/a/b/text()')) != 0 and len(div.xpath('./div[1]/div[2]/text()')) !=0:
            	# 解析数据
                title = div.xpath('./div[1]/p[1]/a/b/text()')[0].extract()
                content = div.xpath('./div[1]/div[2]/text()')[0].extract()

然后在item类中定义相关的属性,将解析的数据存储到item类型的对象(items.py里面有个FirstItem类,可以实例化对象,即item类的实例对象)。修改items.py文件,定义相关属性,比如诗歌、内容,将解析到的数据封装到item对象中:

class FirstItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()
    pass

用item实例化对象后,这个对象就能取得title和content。将item类型的对象提交给管道进行持久化存储操作。pipelines.py中定义了FirstPipeline类,专门用来处理item类型对象。在管道类的process_item中要将其接受到的item对象中存储的数据进行持久化存储操作

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


class FirstPipeline:
    fp = None
    # 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
    def open_spider(self, spider):
        print('开启爬虫')
        self.fp = open('./a.txt', 'w', encoding='utf-8')

    # 用来处理item类型对象
    # 该方法可以接收爬虫文件提交过来的item对象
    # 该方法每接收到一个人item就会被调用一次
    # 如果把打开关闭文件放这,则需要多次打开关闭文件,则另外创建函数,减少打开关闭文件次数
    def process_item(self, item, spider): # item为item对象
        title = item['title']
        content = item['content']
        self.fp.write(title+':'+content+'\n')
        return item

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

在配置文件setting.py中开启管道,即取消管道的注释

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   "first.pipelines.FirstPipeline": 300,
}

最后在主函数main.py中添加调库及将解析的数据存储到item类型的对象和将item提交给管道的代码

import scrapy
from first.items import FirstItem

class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.gushiwen.cn/"]

    def parse(self, response):
        # 解析数据
        # xpath返回的是列表,列表元素一定是Selector类型的对象
        div_list = response.xpath('/html/body/div[2]/div[1]/div')
        for div in div_list:
            # extract可以将selector对象中data参数存储的字符串提取出来(可以是列表)
            # 解析诗歌标题和内容
            # 加个if判断,如果解析到的不为空,就进行存储
            if len(div.xpath('./div[1]/p[1]/a/b/text()')) != 0 and len(div.xpath('./div[1]/div[2]/text()')) !=0:
                title = div.xpath('./div[1]/p[1]/a/b/text()')[0].extract()
                content = div.xpath('./div[1]/div[2]/text()')[0].extract()

                # 将解析的数据存储到item类型的对象
                item = FirstItem()
                item['title'] = title
                item['content'] = content
                # 将item提交给管道,进行数据存储
                yield item

最后运行程序:scrapy crawl main

3、数据同时保存至本地及数据库

举例:将爬取的数据一份存到本地,一份存到数据库
在上面代码的基础上,再在管道中定义多个管道类,一个类制定存储到某个地方(一个存储到本地,一个存储到数据库)
在piplines.py中新增如下类

# 导入数据库
# import pymysql
# 管道文件中一个管道类对应将一组数据存储到一个平台或者载体中
class mysqlPileLine(object):
    conn = None
    cursor = None
    def open_spider(self, spider):
        self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123456', db='qiubai', charset='utf8')
    def process_item(self, item, spider):
        self.cursor =self.conn.cursor()
        try:
           self.cursor.execute('insert into qiubai values("%s","%s")'%(item["title"],item["content"]))
           self.conn.commit()
        except Exception as e:
            pritn(e)
            self.conn.rollback()
        return item

    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()

在settings.py中开启管道,添加个优先级

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    # 300表示优先级,数值越小,优先级越高
    "first.pipelines.FirstPipeline": 300,
    "first.pipelines.mysqlPileLine": 301,
}

爬虫文件提交的item类型的对象最终会提交给先执行的类(优先级高的类)。在优先级高的里面添加return item,return就会传递给下一个即将执行的管道类,pipelines.py优先级高的类中给一个函数添加return

    def process_item(self, item, spider): # item为item对象
        title = item['title']
        content = item['content']
        self.fp.write(title+':'+content+'\n')
        # return就会传递给下一个即将执行的管道类
        return item

管道文件中一个管道类对应的是将数据存储到一个平台。爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收(优先级高的)。process_item中的return item表示将item传递给下一个即将被执行的管道类

4、基于spider爬取某网站各页面数据

以某网站为例,对某网站的全部页面(有分页栏)对应的页面进行爬取,这里需要获取所有页面的URL,并将其加入到start_url中,然后进行请求发送

首先创建项目并初始化操作

1、打开pycharm的terminal
2、scrapy startproject first
3、cd first
4、scrapy genspider main www.xxx.com
5、修改settings.py里面的ROBOTSTXT_OBEY = True改为False并添加LOG_LEVEL='ERROR'
6、scrapy crawl main  (最后一步运行)

然后编辑main.py文件

import scrapy


class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.xxx.com"]
    # 第一个页面
    start_urls = ["https://www.woyaogexing.com/touxiang/katong/new/index.html"]
    
    # 生成一个通用的url
    # %d可以被page_num取代
    url = 'https://www.woyaogexing.com/touxiang/katong/new/index_%d.html'
    # 页码数
    page_num = 2

    def parse(self, response):
        # 解析数据
        div_list = response.xpath('/html/body/div[3]/div[3]/div[1]/div[2]/div')
        for li in div_list:
            img_name = li.xpath('./a[2]/text()')[0].extract()
            print(img_name)

        # 递归
        if self.page_num <= 5:  # 前5页
            # 每个页码对应的url,page_num替换url中的%d
            new_url = format(self.url%self.page_num)
            self.page_num += 1
            # 手动请求发送:callback回调函数是专门用于数据解析
            yield scrapy.Request(url=new_url, callback=self.parse)

5、爬取本页和详情页信息(请求传参)

爬取本页面信息,然后再爬取详情页面信息,即本页面点击进入详情页之后爬取详细信息,这需要解析当前页获取详情页URL,然后对详情页URL发起请求并解析,然后存储。在scrapy.Request中传入meta={‘item’:item}参数,即传入item

import scrapy
from first.items import FirstItem

class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.baidu.com"]
    # 第一页面URL
    start_urls = ["https://www.qidian.com/rank/yuepiao/"]
	# 后续页面URL
    url = 'https://www.qidian.com/rank/yuepiao/year2023-month03-page%d/'
    page_num = 2
    
    # 回调函数接收item,解析详情页
    def parse_detail(self,response):
        item = response.meta['item']
        # 这里解析详情页信息
        content = response.xpath('...')
        item['content'] = content
        print(item['title'], item['content'])
        yield item

    # 解析首页的岗位名称
    def parse(self, response):
        li_list = response.xpath('/html/body/div[1]/div[6]/div[2]/div[2]/div/div/ul/li')
        for li in li_list:
            item = FirstItem()
            title = li.xpath('./div[2]/h2/a/text()')[0].extract()
            item['title'] = title
            detail_url = 'https:' + li.xpath('./div[2]/h2/a/@href')[0].extract()
            # 对详情页发送请求获取详情页的信息源码数据
            # 手动请求的发送
            # 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
            yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})

        # 分页操作,爬取前5页数据
        if self.page_num <= 5:
            new_url = format(self.url%self.page_num)
            self.page_num += 1
            yield scrapy.Request(new_url, callback=self.parse)

6、图片数据爬取ImagesPipeline

基于scrapy爬取字符串类型的数据后可以直接提交给管道进行存储,但爬取图片需要解析图片的src,然后单独对图片的地址发起请求后去图片二进制类型的数据。pipelines.py里面的类只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会进行持久化存储

在配置文件中添加文件存储目录,修改settings.py

#修改
ITEM_PIPELINES = {
    # 300表示优先级,数值越小,优先级越高
    "first.pipelines.imgPipeline": 300,
}

# 在配置文件末尾添加文件存储目录
IMAGES_STORE = './imgs'

pipelines.py文件内容全部删除,修改为如下内容

from scrapy.pipelines.images import ImagesPipeline
import scrapy

class imgPipeLine(ImagesPipeline):

    # 可以根据图片地址进行图片数据的请求
    def get_media_requests(self, item, info):
        yield scrapy.Request(item['src'])
    # 指定图片存储的路径
    def file_path(self, request, response=None, info=None, *, item=None):
        imgName = request.url.split('/')[-1]
        return imgName

    def item_completed(self, results, item, info):
        # 返回给下一个即将被执行的管道类
        return item

main.py内容如下

import scrapy
from first.items import FirstItem

class MainSpider(scrapy.Spider):
    name = "main"
    # allowed_domains = ["www.baidu.com"]
    start_urls = ["..."]

    def parse(self, response):
        li_list = response.xpath('...')
        for li in li_list:
            item = FirstItem()
            src = li.xpath('...').extract_first()
            # 图片地址
            item['src'] = src
            yield item

五、中间件

下载中间件在引警和下载器之间,批量拦截到整个工程中所有的请求和响应
拦截请求作用:UA伪装(在process_request函数中),代理IP(在process_exception函数中 ,这里一定需要return request将修正之后的请求对象进行重写的请求发送)
拦截响应作用:篡改响应数据,响应对象

1、拦截请求中间件(UA伪装,代理IP)

1、打开pycharm的terminal
2、scrapy startproject first
3、cd first
4、scrapy genspider main www.xxx.com
5、修改settings.py里面的ROBOTSTXT_OBEY = True改为False并添加LOG_LEVEL='ERROR'
6、scrapy crawl main  (最后一步运行)

项目中的middlewares.py就是对应的中间件,MiddleproSpiderMiddleware类对应的就是爬虫中间件,这里不用可以删除,用的是下载中间件,即MiddleproDownloaderMiddleware类。MiddleproDownloaderMiddleware类中重点的是三个函数,即process_request(拦截请求),process_response(拦截所有响应),process_exception(拦截发送异常请求)

middlewares.py文件内容如下

class MiddleproDownloaderMiddleware:
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    # UA池
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]
    # ip
    PROXY_http = ['153.180.102.104:80','195.208.131.189:56055']
    PROXY_https = ['120.83.49.90:9000', '95.189.112.214:35508']
    # 拦截请求
    def process_request(self, request, spider):
        # UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None
        
    # 拦截所有响应
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response
        
    # 拦截发送异常请求
    def process_exception(self, request, exception, spider):
        # 当请求IP被禁用,爬取失败,进入到这里
        # 代理
        if request.url.split(':')[0] == 'http':
            request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http)
        else:
            request.meta['proxy'] = 'http://' + random.choice(self.PROXY_https)
        # 将修正之后的请求对象进行重新的请求发送
        return request

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)

settings.py中的如下内容的注释取消

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    "middlePro.middlewares.MiddleproDownloaderMiddleware": 543,
}

main.py主函数内容如下:

import scrapy


class MiddleSpider(scrapy.Spider):
    # 爬取百度(搜索ip)
    name = "middle"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.baidu.com/s?wd=ip"]

    def parse(self, response):
        page_text = response.text
        with open('./ip.html', 'w', encoding='utf-8') as fp:
            fp.write(page_text)

2、拦截响应中间件(动态加载)

这里还是用的下载中间件,用来篡改响应数据,响应对象。以爬取某页面数据(标题和内容)为例,通首页解析一些标题栏的板块对应详情页的url(没有动态加载),每一个板块对应的标题都是动态加载出来的(动态加载),通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出其详细的内容

首先创建项目

1、打开pycharm的terminal
2、scrapy startproject wangyipro
3、cd wangyipro
4、scrapy genspider main www.xxx.com
5、修改settings.py里面的ROBOTSTXT_OBEY = True改为False并添加LOG_LEVEL='ERROR'
6、scrapy crawl main  (最后一步运行)

注:标题栏每个标题栏里面固定位子的解析方式可能不一样,可能不能解析到正确的内容

settings.py取消如下注释

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    "wangyiPro.middlewares.WangyiproDownloaderMiddleware": 543,
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    "wangyiPro.pipelines.WangyiproPipeline": 300,
}

修改items.py

import scrapy


class WangyiproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    content = scrapy.Field()
    pass

pipelines.py添加输出

class WangyiproPipeline:
	# 这里存储数据
    def process_item(self, item, spider):
        print(item)
        return item

修改middlewares.py内容

from scrapy.http import HtmlResponse
from time import sleep

class WangyiproDownloaderMiddleware:
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None
    # 通过该方法拦截各特定板块对应的响应对象,进行篡改
    def process_response(self, request, response, spider): # spider爬虫对象
        # 获取爬虫类转给你定义的浏览器对象
        bro = spider.bro
        # 挑选出指定的响应对象进行篡改
        # 通过url指定让request
        # 通过request指定response
        if request.url in spider.models_urls:
            # 指定的个板块对应的url进行请求
            bro.get(response.url)
            sleep(2)
            # 包含了动态加载的新闻数据
            page_text = bro.page_source
            # response # 特定板块对应的响应对象
            # 针对定位到的这些response进行篡改
            # 实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象
            # 基于selenium便捷的获取动态加载数据
            new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
            return new_response
        else:
            # response # 其他请求对应的响应对象
            return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)

编写主函数main.py

import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem


class WangyiSpider(scrapy.Spider):
    name = "wangyi"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["..."]
    # 存储各板块url
    models_url = []
    # 解析各板块对应详情页的url
    # 实例化浏览器对象
    def __init__(self):
        self.bro = webdriver.Chrome(executable_path='')
    def parse(self, response):
        li_list = response.xpath('...')
        # 解析获取每个标题栏的URL,并存储到列表中
        for li in li_list:
            model_url = li.xpath('...')[0].extract()
            self.models_url.append(model_url)

        # 依次对每个标题栏对应的页面进行请求
        for url in self.models_url:# 对每个板块的url进行请求发送
            yield scrapy.Request(url, callback=self.parse_model)

    # 每一个标题栏对应的内容标题相关的内容都是动态加载
    # 解析每一个板块页面中对应新闻的标题和新闻详情页的url
    def parse_model(self, response):
        # 因为这里内容是动态加载出来的,所以用通常的方法response.xpath()是抓取不到的,需要在middleware.py文件中的process_response函数中编辑
        # 标题栏每个标题栏里面固定位子的解析方式可能不一样,需要分析每个页面的解析方式,可以根据属性值匹配
        div_list = response.xpath('...')
        for div in div_list:
            title = div.xpath('').extract_first()
            new_detail_url = div.xpath('').extract_first()

            item = WangyiproItem()
            item['title'] = title

            # 对新闻详情页的url发起请求
            yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item':item})
    # 解析新闻内容
    def parse_detail(self, response):
        content = response.xpath('').extract()
        content = ''.join(content)
        item = response.mate['item']
        item['content'] = content
        yield item

    def close(self, spider):
        self.bro.quit()

六、CrawlSpider(自动请求全站爬取,全部页面,自动下拉滚轮爬取)

可以提取页面显示栏中显示及未显示页面的所有页码链接等信息

CrawlSpider是Spider的一个子类,和Spider(手动请求)一样可以爬取全站数据
链接提取器:根据指定规则(参数allow=“正则”)进行指定链接的提取
规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作

爬取全站数据:爬取的数据没有在同一个页面,多个页码
1.可以使用链接提取器提取所有的页码链接
2.让链接提取器提取所有的详情页链接

CrawlSpider的使用(加 -t crawl):

1、打开pycharm的terminal
2、scrapy startproject first
3、cd first
4、scrapy genspider -t crawl main www.xxx.com
5、修改settings.py里面的ROBOTSTXT_OBEY = True改为False并添加LOG_LEVEL='ERROR'
6、scrapy crawl main  (最后一步运行)

items.py创建两个item类,

import scrapy


class SunproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    new_num = scrapy.Field()

class DetailItem(scrapy.Item):
    new_id = scrapy.Field()
    content = scrapy.Field()

pipelines.py

class SunproPipeline:
    def process_item(self, item, spider):
        # 如何判定item类型
        if item.__class__.__name__ == 'DetailItem':
            print(item['new_id'],item['new_content'])
        else:
            print(item['new_num'],item['new_title'])
        return item

sun.py主函数

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.item import SunproItem,DetailItem

# 需求爬取某网站的所有标签页的所有内容
class SunSpider(CrawlSpider):
    name = "sun"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["http://www.xxx.com/"]
    # 链接提取器:根据指定规则(allow="正则")进行指定链接的提取
    # 提取页码链接
    link = LinkExtractor(allow=r"Items/")
    # 提取详情页链接
    link_detail = LinkExtractor(allow=r"...") #
    # Rule为规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作
    # Rule参数:链接提取器
    # follow改为True可以将链接提取器 继续作用到 链接提取器提取到的链接 所对应的页面中
    # follow为True可以提取下面显示及未显示页面的所有页码链接
    # follow为False只能提取下面显示的几个页面的链接
    # Rule中的callback调用对应parse_item函数
    rules = (Rule(link, callback="parse_item", follow=True),  # 提取页面链接
             Rule(link_detail, callback='parse_detail'))      # 提取详情页链接

    # 如下两个解析方法中是不可以实现请求传参
    # 如果将两个解析方法解析的数据存储到同一个item中,可以依次存储到两个item中,在items.py文件中建两个item类
    def parse_item(self, response):
        # xpath表达式中不可以出现tbgodybiao标签
        tr_list = response.xpath('...')
        for tr in tr_list:
            new_num = tr.xpath('...').extract_first()
            new_title = tr.xpath('...').extract_first()
            item = SunproItem()
            item['new_num'] = new_num
            item['new_title'] = new_title

    # 解析详情页内容
    def parse_detail(self, response):
        new_id = response.xpath('...')
        new_content = response.xpath('...')
        item = DetailItem()
        item['new_id'] = new_id
        item['new_content'] = new_content

七、分布式爬虫

搭建一个分布式的机群(多台电脑),当其中一台发出请求(多个url需要爬取),其他机器会一起爬取数据,提高效率

概念:需要搭建一个分布式的机群,让其对一组资源进行分布式联合爬取
作用:提升爬取数据效率

安装scrapy-redis组件pip install scrapy-redis
原生的scrapy不可以实现分布式爬虫,必须让scrapy组合这scrapy-redis组件一起实现分布式爬虫
原生的scrapy不可以实现分布式爬虫是因为调度器和管道都不可以被分布式机群共享
scrapy-redis组件可以给原生的scrapy框架提供可以被共享的管道和调度器

实现流程
创建一个工程
创建一个基于CrawlSpider的爬虫文件
修改当前的爬虫文件:

导包:from scrapy_redis.spoders import RedisCrawlSpider
将start_urls和allowed_domains进行注释
添加一个属性:redis_key = 'sun' 可以被空闲的调度器队列名称
编写数据解析相关的操作
将当前爬虫类的父类修改成RedisCrawlSpider

主函数

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

from scrapy_redis.spiders import RedisCrawlSpider

# 需求爬取某网站的所有标签页的所有内容
class SunSpider(RedisCrawlSpider):
    name = "sun"
    # allowed_domains = ["www.xxx.com"]
    # start_urls = ["http://www.xxx.com/"]
    redis_key = 'sun'

    link = LinkExtractor(allow=r"Items/")
    link_detail = LinkExtractor(allow=r"...")
    rules = (Rule(link, callback="parse_item", follow=True),
             Rule(link_detail, callback='parse_detail'))

    def parse_item(self, response):
        # xpath表达式中不可以出现tbgodybiao标签
        tr_list = response.xpath('...')
        for tr in tr_list:
            new_num = tr.xpath('...').extract_first()
            new_title = tr.xpath('...').extract_first()
            item = SunproItem()
            item['new_num'] = new_num
            item['new_title'] = new_title

            yield item

修改配置文件settings,除了和上面常规的修改,还有添加如下内容

# 指定使用可以被共享的管道
ITEM_PIPELINES = {
	'scrapy_redis.pipelines.RedisPipeline':400
}
# 指定调度器
# 增加一个去重容器类的配置,作用使用Redis的set集合来存储请求的指纹数据,从而实现请求取重的持久化存储
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy_redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化,也就是当爬虫结束了,要不要清空Redis中请求队列和去重指纹的set,如果True
SCHEDULER_PERSIST = True

# 指定redis服务器
REDIS_HOST = 'redis服务器ip地址'  # 写成redis远程服务器的ip
REDIS_PORT = 6379

redis相关操作配置:

配置redis配置文件
	linux或mac:redis.conf
	windows:redis.windows.conf
	打开配置文件修改:
		将bind 127.0.0.1注释
		关闭保护模式:protected-mode yes改为no
结合着配置文件开启redis服务
	redis-server 配置文件
启动客户端
	redis-cli

执行工程:scrapy runspider xxx.py
向调度器的队列中放入一个起始的url

调度器的队列在redis的客户端中
	lpush xxx www.xxx.com

爬取到的数据存储在redis的proName:items这个数据结构中

八、增量式爬虫

比如某个网站一定时间会更新一部分内容,有些不会更新,今天我们爬取了网站的所有内容,明天再爬取的时候,我们只需要爬取比昨天新增的内容,原先的不用再爬取,这就是增量式爬虫(如下核心部分)

检测网站数据更新的情况,只会爬取网站最新更新出来的数据
分析:

指定起始url:www.4567tv.tv
基于CrawlSpider获取其他页码链接
基于Rule将其他页码链接进行请求
从每一个页码对应的页面源码中解析出每一个电影详情页的URL
核心:检测电影详情页的url之前有没有请求过
	  将爬取过的电影详情页的URL存储
	 存储到redis的set数据结构(自动清楚重复数据,即存在过添加不进去,返回1表示不存在可以添加,返回0表示存在不添加)
对详情页的url发起请求,然后解析出电影的名称和简介
进行持久化存储

pipelines.py

from redis import Redis
class SunproPipeline:
    conn = None
    def open_spider(self, spider):
        self.conn = spider.conn
    def process_item(self, item, spider):
        dic = {
            'name':item['name']
        }
        self.conn.lpush('movieData', dic)
        return item

主函数

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.item import SunproItem,DetailItem
from redis import Redis

from scrapy_redis.spiders import RedisCrawlSpider

class SunSpider(RedisCrawlSpider):
    name = "sun"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["http://www.xxx.com/"]

    link = LinkExtractor(allow=r"Items/")
    rules = (Rule(link, callback="parse_item", follow=True))
    # 创建redis链接对象
    conn = Redis(host='127.0.0.1', port=6379)

    def parse_item(self, response):
        # xpath表达式中不可以出现tbgodybiao标签
        tr_list = response.xpath('...')
        for tr in tr_list:
            detail_url = tr.xpath('...').extract_first()
            # 将详情页的url存入redis的set中
            ex = self.conn.sadd('urls', detail_url)
            # ex=1表示数据结构中不存在该url,即没爬取过,可以爬取
            # ex=0表示数据结构中存在该url,即之前爬取过,不用爬取
            if ex == 1:
                print('该url没有被爬取过')
                yield scrapy.Request(url=detail_url, callback=self.parst_detail)
            else:
                print("该url爬取过,还没更新")

    # 解析详情页中的电影名称和类型,进行持久化存储
    def parst_detail(self, response):
        item = SunproItem()
        item['name'] = response.xpath('')
        yield item

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IP欺骗种类有哪些?

每台计算机都有一个IP地址&#xff0c;发送的任何数据都被分成许多块&#xff08;“数据包”&#xff09;&#xff0c;每个数据包单独传输&#xff0c;当这些数据包到达链的末端时&#xff0c;就会重新组装并作为一个整体呈现。此外&#xff0c;每个数据包还有其可识别信息&…

4、High-Resolution Image Synthesis with Latent Diffusion Models

简介github地址diffusion model明显的缺点是耗费大量的时间、计算资源&#xff0c;为此&#xff0c;论文将其应用于强大的预训练自编码器的潜在空间 &#xff0c;这是首次允许在复杂性降低和细节保存之间达到一个近乎最佳的点&#xff0c;极大地提高了视觉保真度。通过在模型架…

操作系统复习题

什么是线程&#xff1f; 线程&#xff08;Thread&#xff09;&#xff1a;轻量级进程&#xff0c;是操作系统进行调度的最小单位。一个线程是一个任务&#xff08;一个程序段&#xff09;的一次执行过程。线程不占有内存空间&#xff0c;它包括在进程的内存空间中。在同一个进程…

自然语言处理历史最全预训练模型(部署)汇集分享

什么是预训练模型&#xff1f;预练模型是其他人为解决类似问题而创建的且已经训练好的模型。代替从头开始建立模型来解决类似的问题&#xff0c;我们可以使用在其他问题上训练过的模型作为起点。预训练的模型在相似的应用程序中可能不是100&#xff05;准确的。本文整理了自然语…

踩坑:maven打包失败的解决方式总结

Maven打包失败原因总结如下&#xff1a; 失败原因1&#xff1a;无法使用spring-boot-maven-plugin插件 使用spring-boot-maven-plugin插件可以创建一个可执行的JAR应用程序&#xff0c;前提是应用程序的parent为spring-boot-starter-parent。 需要添加parent的包spring-boot…

QML组件

一个QML文件定义了一个独立的、顶级的QML组件。 一个QML组件就是一个模板&#xff0c;被QML运行环境解释来创建一个带有一些预定义行为的对象。 一个独立的QML组件可以运行多次来禅城多个对象&#xff0c;每个对象都可以称为该组件的实例。 例子&#xff1a; 在项目中添加一…

Redis基础入门

文章目录前言一、redis是什么&#xff1f;二、安装步骤1.下载安装包2.安装三、Redis的数据类型redis是一种高级的key-value的存储系统&#xff0c;其中的key是字符串类型&#xff0c;尽可能满足如下几点&#xff1a;字符串(String)列表(List)集合(Set&#xff0c;不允许出现重复…

MySQL面试题-索引篇

1.什么是索引 MySQL的索引是一种数据结构&#xff0c;可以用于加快数据库中数据的查询速度。索引是基于表中一个或多个列的值排序的快速查找数据结构&#xff0c;可以大大提高查询效率。MySQL支持多种类型的索引&#xff0c;如B-tree索引、哈希索引、全文索引等。 索引可以在…

【java基础】异常处理(Exception)

文章目录基本介绍异常分类抛出异常非检查型异常检查型异常捕获异常捕获单个异常捕获多个异常创建自定义异常类finally字句try-with-Resource总结基本介绍 对于一个程序&#xff0c;总是有bug的。如果我们的程序遇到一个错误就终止了&#xff0c;那么肯定是不合理&#xff0c;程…

数据爬取(urllib+BeautifulSoup)

文章目录知识点总结爬虫步骤爬虫三要素爬虫注意事项python爬取技术学习网页抓取库Urllib网页解析库Beautifulsoup案例知识点总结 爬虫是一种按照一定规则&#xff0c;自动抓取互联网上网页中的相应信息的程序或脚本。 爬虫步骤 1.需求分析 2.找到要爬取信息的网站 3.下载reque…

基于halo后台管理+Gblog-wx搭建的微信小程序

先决条件 1、已经通过docker安装了halo后台管理系统(参考:http://43.136.39.20:8090/archives/halo-build) 2、安装的halo版本为1.5.3版本。此版本的halo才能安装小程序主题并启动小程序 3、需要修改小程序文件配置 解决安装的不是1.5.3的halo 1、如果是docker安装的halo…

蓝牙技术|蓝牙5.4标准正式发布,蓝牙ESL电子价签迎来一波利好

蓝牙技术联盟于2023年1月31日批准了蓝牙核心规范v5.4版本(以下简称蓝牙5.4版本)&#xff0c;并已正式公布。 蓝牙5.4版本引入了四个新特性&#xff0c;如下: 广播数据加密&#xff08;Encrypted Advertising Data&#xff09;&#xff1a;对广播数据进行加密以提高广播数据传…

[神经网络]Swin Transformer网络

一、概述 Swin Transformer是一个用了移动窗口的层级式Vision Transformer。 在图像领域&#xff0c;Transformer需要解决如下两个问题&#xff1a; ①尺度问题&#xff1a;同一语义的物体在图像中有不一样的尺度。(大小不同) ②Resolution过大&#xff1a;若以像素点作为单位&…

利用python写一个gui小公举--环境搭建

文章目录背景搭建环境安装必要库添加工具快捷方式检验背景 在实习过程中遇到一个问题&#xff0c;某项目是通过python代码实现的&#xff0c;而且需要一直修改参数实现功能&#xff0c;过程有些繁琐。虽然师兄用PHP study搭了一个网站用于查看结果&#xff0c;但是还是过于繁琐…

分布式新闻项目实战 - 12.热点文章-实时计算(kafkaStream)

死海效应&#xff1a; 公司发展到一定阶段后&#xff0c;工作能力强的员工&#xff0c;就会离职&#xff0c;因为他无法容忍公司的某些行为&#xff0c;即使辞职也很快会找到好工作&#xff1b;工作能力差的员工&#xff0c;却赖着不走&#xff0c;因为辞职以后也不太好找工作&…

JavaScript实现十大排序算法

目录 概览 一、冒泡排序 1、算法描述 2、图示 3、代码 二、选择排序 1、算法描述 2、图示 3、代码 三、插入排序 1、算法描述 2、图示 ​编辑 3、代码 四、希尔排序 1、算法描述 2、图示 3、代码 五、并归排序 1、算法描述 2、图示 ​编辑​编辑3、代码 …

食品与疾病关系预测赛题

和鲸平台数据分析实战 题目&#xff1a;食品与疾病关系预测算法赛道 一、赛题描述 食品与疾病关系预测算法赛道 越来越多的证据表明&#xff0c;食物分子与慢性疾病之间存在关联甚至治疗关系。营养成分可能直接或间接地作用于人类基因组&#xff0c;并调节参与疾病风险和疾病…

php结课报告--会员注册管理系统

目录 1&#xff0e; 系统背景及意义 1 2&#xff0e; 系统的设计思路 1 2.1 数据库设计分析 1 2.2 功能模块设计分析 1 3&#xff0e; 程序功能测试及截图 1 3.1代码测试与功能演示 1 4&#xff0e; 总结与收获 6 1&#xff0e;系统背景及意义 随着现在时代得发展&#xff0c;…

【AI面试】NMS 与 Soft NMS 的辨析

往期文章&#xff1a; AI/CV面试&#xff0c;直达目录汇总【AI面试】L1 loss、L2 loss和Smooth L1 Loss&#xff0c;L1正则化和L2正则化 一、NMS 非极大值抑制&#xff08;Non-Maximum Suppression&#xff0c;NMS&#xff09;&#xff0c;并不是深度学习时期&#xff0c;目标…

VS项目配置常用的配置

背景随着学习使用VS的深入在项目配置使用一些相对路径是必不可少的,使用绝对路径是最简单的,但是加入你换了电脑或者别人拉取你的代码,就会发现通常会编译不过.因为项目配置使用了绝对路径.所以使用相对路径的好处就会体现.在VS项目配置有自己的一套配置,简单记录一下我使用到的…