前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Scrapy框架(二):项目实战

Scrapy框架(二):项目实战

作者头像
花猪
发布2022-02-26 10:20:18
1.1K0
发布2022-02-26 10:20:18
举报

前言

以爬取github信息为例,介绍Scrapy框架用法。

目标:根据github关键词搜索,爬取所有检索结果。具体包括名称链接starsUpdatedAbout信息。

项目创建

开启Terminal面板,创建一个名为powang的scrapy的工程:

scrapy startproject powang

进入创建的工程目录下:

cd powang

在spiders子目录中创建一个名为github的爬虫文件:

scrapy genspider github www.xxx.com

说明:网址可以先随便写,具体在文件中会修改

执行爬虫命令:

scrapy crawl spiderName

如本项目执行命令:scrapy crawl github

项目分析与编写

settings

首先看配置文件,在编写具体的爬虫前要设置一些参数:

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# 设置只显示错误类型日志
LOG_LEVEL = 'ERROR'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56'

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

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

# 设置请求重试
RETRY_TIMES = 100 # 最大重试次数
RETRY_ENABLED = True # 重试开启(默认开)
RETRY_HTTP_CODES = [500, 503, 504, 400, 403, 408, 429] # 重试的错误类型

# 下载延时
DOWNLOAD_DELAY = 2 # 设置发送请求的延时
RANDOMIZE_DOWNLOAD_DELAY = True # 开启随机请求延时

说明:

  • ROBOTSTXT_OBEY:默认遵守robots协议,很多网站都有该协议(为了防止爬虫对不必要信息的爬取)。这里为了项目测试,选择关闭(False)
  • LOG_LEVEL:设置日志打印等级,这里设置为仅打印错误类型日志信息。(需要手动添加)
  • USER_AGENT:在请求头中添加UA信息,用于跳过UA拦截。也可以直接在中间件中配置UA池(更推荐后者)
  • DOWNLOADER_MIDDLEWARES:开启下载中间件。在middlewares.py(中间件)中会设置诸如UA池、IP池等配置。
  • ITEM_PIPELINES:用于开启item配置。(下文会讲到关于item的作用)
  • 请求重试(scrapy会自动对失败的请求发起新一轮尝试):
    • RETRY_TIMES:设置最大重试次数。在项目启动后,如果在设定重试次数之内还无法请求成功,则项目自动停止。
    • RETRY_ENABLED:失败请求重试(默认开启)
    • RETRY_HTTP_CODES:设定针对特定的错误代码发起重新请求操作
  • 下载延时:
    • DOWNLOAD_DELAY:设置发送请求的延时
    • RANDOMIZE_DOWNLOAD_DELAY:设置随机请求延时
  • 配置管道以及中间件的数字表示优先级,数值越小,优先级越高。

爬虫文件

默认文件如下:

import scrapy

class GithubSpider(scrapy.Spider):
    name = 'github'
    allowed_domains = ['www.xxx.com']
    start_urls = []

    def parse(self, response):
        pass

说明:

  • name:爬虫文件的名称,即爬虫源文件的一个唯一标识
  • allowed_domains:用来限定start_urls列表中哪些url可以进行请求发送(通常不会使用)
  • start_urls:起始的url列表。该列表中存放的url会被scrapy自动进行请求的发送(可以设置多个url)
  • parse:用于数据解析。response参数表示的就是请求成功后对应的响应对象(之后就是直接对response进行操作)

分析:

以搜索结果hexo为例:

每一条结果的名称链接stars以及Updated都是可以在搜索页直接获取的,

但是有些过长的About信息在搜索页展示并不全,只得通过点击详情页进行获取。

以及最后要爬取全部信息,需要分页爬取。

代码编写

首先编写一个起始的url和一个用于分页通用的url模板:

# 检索关键词
keyword = 'vpn' 
# 查询的起始页数
pageNum = 1

# 起始url
start_urls = ['https://github.com/search?q={keyword}&p={pageNum}'.format(keyword=keyword, pageNum=pageNum)]

# 通用url模板
url = 'https://github.com/search?p=%d&q={}'.format(keyword)

编写parse函数(搜索结果页面分析):

def parse(self, response):

    status_code = response.status  # 状态码

    #========数据解析=========
    page_text = response.text
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//*[@id="js-pjax-container"]/div/div[3]/div/ul/li')
    for li in li_list:
        # 创建item对象
        item = PowangItem()
        # 项目名称
        item_name = li.xpath('.//a[@class="v-align-middle"]/@href')[0].split('/', 1)[1]
        item['item_name'] = item_name
        # 项目链接
        item_link = 'https://github.com' + li.xpath('.//a[@class="v-align-middle"]/@href')[0]
        item['item_link'] = item_link
        # 项目最近更新时间
        item_updated = li.xpath('.//relative-time/@datetime')[0].replace('T', ' ').replace('Z', '')
        item_updated = str(datetime.datetime.strptime(item_updated, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=8))  # 中国时间
        item['item_updated'] = item_updated
        # 项目stars(解决没有star的问题)
        try:
            item_stars = li.xpath('.//a[@class="Link--muted"]/text()')[1].replace('\n', '').replace(' ', '')
            item['item_stars'] = item_stars
        except IndexError:
            item_stars = 0
            item['item_stars'] = item_stars
        else:
            pass
        # 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
        yield scrapy.Request(item_link, callback=self.items_detail,meta={'item':item})

    # 分页操作
    new_url = format(self.url % self.pageNum)
    print("===================================================")
    print("第" + str(self.pageNum) + "页:" + new_url)
    print("状态码:" + str(status_code))
    print("===================================================")
    self.pageNum += 1
    yield scrapy.Request(new_url, callback=self.parse)

说明:

  • response.status:可以获取响应状态码
  • 为了后期对爬取到的数据进行进一步操作(如:存储),需要将每一条数据进行item对象的封装
# 创建item对象
item = PowangItem()
# ....
# 封装
item['item_name'] = item_name
item['item_link'] = item_link
item['item_updated'] = item_updated
item['item_stars'] = item_stars
  • yield:

为了获取About内容,需要对爬取到的url再进行访问以获取到详情页面,这时就可以使用yield发送访问请求:

格式:yield scrapy.Request(url, callback=xxx,meta={'xxx':xxx})

yield scrapy.Request(item_link, callback=self.items_detail,meta={'item':item})
  • url:即详情页的url
  • callback:回调函数(可以编写其他函数,也可以是自己(递归))。即携带url发起请求,并交给回调函数进行处理,在其中的response处理信息
  • meta:字典形式,可以将该函数中的item对象继续交由下一个回调函数进行下一步处理
  • 分页操作:利用yield递归式发起请求,处理不同页面的数据

编写items_detail函数(结果详情页分析):

为了获取About信息,需要对搜索结果的详情页进行分析。

def items_detail(self, response):

    # 回调函数可以接收item
    item = response.meta['item']

    page_text = response.text
    tree = etree.HTML(page_text)
    # 项目描述
    item_describe = ''.join(tree.xpath('//*[@id="repo-content-pjax-container"]/div/div[3]/div[2]/div/div[1]/div/p//text()')).replace('\n', '').strip().rstrip();
    item['item_describe'] = item_describe

    yield item

说明:

  • 利用response.meta['xxx']可以接收上一个函数传来的参数(如:接收item)
  • 如果在经过一系列回调函数操作后对item对象封装完毕,在最后一个函数需要利用yielditem交由给管道处理

完整的爬虫文件如下:

import datetime

from lxml import html
etree = html.etree

import scrapy
from powang.items import PowangItem


class GithubSpider(scrapy.Spider):
    name = 'github'

    keyword = 'hexo' # 检索关键词
    # 查询的起始页数
    pageNum = 1

    start_urls = ['https://github.com/search?q={keyword}&p={pageNum}'.format(keyword=keyword, pageNum=pageNum)]

    # 通用url模板
    url = 'https://github.com/search?p=%d&q={}'.format(keyword)

    # 解析检索结果页(一级)
    def parse(self, response):

        status_code = response.status  # 状态码

        #========数据解析=========
        page_text = response.text
        tree = etree.HTML(page_text)
        li_list = tree.xpath('//*[@id="js-pjax-container"]/div/div[3]/div/ul/li')
        for li in li_list:
            # 创建item对象
            item = PowangItem()
            # 项目名称
            item_name = li.xpath('.//a[@class="v-align-middle"]/@href')[0].split('/', 1)[1]
            item['item_name'] = item_name
            # 项目链接
            item_link = 'https://github.com' + li.xpath('.//a[@class="v-align-middle"]/@href')[0]
            item['item_link'] = item_link
            # 项目最近更新时间
            item_updated = li.xpath('.//relative-time/@datetime')[0].replace('T', ' ').replace('Z', '')
            item_updated = str(datetime.datetime.strptime(item_updated, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=8))  # 中国时间
            item['item_updated'] = item_updated
            # 项目stars(解决没有star的问题)
            try:
                item_stars = li.xpath('.//a[@class="Link--muted"]/text()')[1].replace('\n', '').replace(' ', '')
                item['item_stars'] = item_stars
            except IndexError:
                item_stars = 0
                item['item_stars'] = item_stars
            else:
                pass
            # 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
            yield scrapy.Request(item_link, callback=self.items_detail,meta={'item':item})

        # 分页操作
        new_url = format(self.url % self.pageNum)
        print("===================================================")
        print("第" + str(self.pageNum) + "页:" + new_url)
        print("状态码:" + str(status_code))
        print("===================================================")
        self.pageNum += 1
        yield scrapy.Request(new_url, callback=self.parse)

    # 解析项目详情页(二级)
    def items_detail(self, response):

        # 回调函数可以接收item
        item = response.meta['item']

        page_text = response.text
        tree = etree.HTML(page_text)
        # 项目描述
        item_describe = ''.join(tree.xpath('//*[@id="repo-content-pjax-container"]/div/div[3]/div[2]/div/div[1]/div/p//text()')).replace('\n', '').strip().rstrip();
        item['item_describe'] = item_describe

        yield item

item

在item提交给管道前,需要先定义字段:

import scrapy

class PowangItem(scrapy.Item):
    item_name = scrapy.Field()
    item_link = scrapy.Field()
    item_describe = scrapy.Field()
    item_stars = scrapy.Field()
    item_updated = scrapy.Field()
    pass

说明: 为了将爬取到的数据更为规范化的传递给管道进行操作,Scrapy为我们提供了Item类。相比于字典(它有点类似与字典)来说更加规范化且更为简洁。

pipelines

对parse传递来的数据进行存储等操作。

import csv
import os

from itemadapter import ItemAdapter

class PowangPipeline:

    file = None # 文件

    def open_spider(self,spider):

        # 文件保存路径
        path = './data'

        isExist = os.path.exists(path)
        if not isExist:
            os.makedirs(path)

        print("开始爬取并写入文件....")
        self.file = open(path + '/github.csv','a', encoding='utf_8_sig', newline="")

    # 用于处理item类型对象
    # 该方法可以接收爬虫文件提交过来的item对象
    # 该方法每接收一个item就会被调用一次
    def process_item(self, item, spider):
        item_name = item['item_name']
        item_link = item['item_link']
        item_describe = item['item_describe']
        item_stars = item['item_stars']
        item_updated = item['item_updated']
        fieldnames = ['item_name', 'item_link', 'item_describe', 'item_stars', 'item_updated']
        w = csv.DictWriter(self.file, fieldnames=fieldnames)
        w.writerow(item)
        return item

    def close_spider(self,spider):
        print('爬取结束....')
        self.file.close()

说明:

  • open_spider():在爬虫开始前执行唯一一次(需要自行重写该方法)
  • process_item():用于处理parse传来的item对象。该方法每接收一个item就会被调用一次
  • close_spider():在爬虫结束后执行唯一一次(需要自行重写该方法)
  • return item:管道类可以编写多个,用以对parse传来的item对象进行不同的操作。而item的传递顺序就是类编写的顺序,通过return item可以将item对象传递给下一个即将被执行的管道类

这里将数据保存至csv文件中。

middlewares

中间件可用于对请求(包括异常请求)进行处理。

直接关注PowangDownloaderMiddleware类(XXXDownloaderMiddleware):

class PowangDownloaderMiddleware:

    # 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池
    Proxys=['127.0.0.1:1087']

    @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):
        # UA伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        # 代理
        proxy = random.choice(self.Proxys)
        request.meta['proxy'] = proxy
        return None

    # 拦截响应
    def process_response(self, request, response, spider):
        return response

    # 拦截发生异常的请求
    def process_exception(self, request, exception, spider):
        pass

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

说明:

  • process_request():用于拦截请求,可设置UA或IP等信息 由于本项目访问github,国内ip不稳定,因此开启代理(本地)
  • process_response():用于拦截响应
  • process_exception():用于拦截发生异常的请求

至此,键入启动命令可以运行项目。

后记

难度不大。

(去年学习的scrapy,一直搁置着没做记录,也就忘了。正好最近项目需要又重新捡了起来)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 项目创建
  • 项目分析与编写
    • settings
      • 爬虫文件
        • 分析:
        • 代码编写
      • item
        • pipelines
          • middlewares
          • 后记
          相关产品与服务
          消息队列 TDMQ
          消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档