欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python 爬虫框架 Scrapy 快速使用

程序员文章站 2022-05-05 15:42:20
...

Scrapy 快速使用


Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中;

Scrapy 官方文档:https://doc.scrapy.org/en/latest/

以下以抓取 CSDN 用户博客数据为例介绍 Scrapy 的快速使用,爬取思路如下:
爬取 CSDN 的用户博客的信息,可在 http://blog.csdn.net/user_id 页面上获取用户博客信息,通过 http://my.csdn.net/user_id 上用户的关注,被关注列表获取其他的用户;

下载安装 Scrapy

可以直接使用 pip 工具下载安装:
pip installl scrapy

初始化项目

这里直接使用官方提供的工具初始化项目,在工作目录下:
scrapy startproject csdn
创建的目录如下:
csdn/
    scrapy.cfg           # 项目配置文件
    csdn/                # 该项目的 python 模块
        __init__.py
        items.py         # 项目中 item 实现类的文件
        pipelines.py     # 项目中 pipeline 实现类的文件
        settings.py      # 项目的设置文件
        spiders/         # 存放 spider 代码的模块
            __init__.py
            ....


定义 Item 类存储数据

Item 是 scrapy 储存爬取数据的容器,并提供了额外的保护机制,以下在 pipelines.py 中实现一个保存用户博客数据的 Item:
# @File: pipelines.py 
import scrapy

# 博客数据对象
class BlogItem(scrapy.Item):
    user_id = scrapy.Field()                   # 用户Id
    user_name = scrapy.Field()                 # 用户名
    visit_count = scrapy.Field()               # 博客访问数
    rank = scrapy.Field()                      # 博客排名


编写 Spider 对象

Spider 是用用于从单个或多个网站爬取数据的类,为主要的爬取逻辑;
创建 Soider 只需要在 spiders 目录下创建一个继承 scrapy.Spider 的类,并覆盖 scrapy() 方法即可,当然也可以通过 scrapy 提供的工具自动创建,进入 csdn 目录,以如下命令:
scrapy genspider <spider_name> <allowed_domains>
创建一个用于爬取博客的爬虫对象 blogSpider 之后填充 blog.py 的代码
# @File: spiders/blog.py 
import scrapy
from scrapy import log
from csdn.items import BlogItem


class BlogSpider(scrapy.Spider):

    # 任务名称
    name = 'blog'
    # 允许访问域名
    allowed_domains = ['my.csdn.net', 'blog.csdn.net']
    # 起始 URL
    start_urls = ['http://my.csdn.net/al_assad']

    # 访问用户主页,获取用户列表
    def parse(self, response):
        blog_page_url = 'http://blog.csdn.net/' + response.url.split('/')[-1]
        scrapy.http.Request(blog_page_url, self.blog_parse)               # 发送blog页面的请求,响应对象的回调方法会 blog_parse
        user_list = response.xpath('//div[@class="header clearfix"]/a/@href').extract()

        for user_name in user_list:
            user_page_url = 'http://my.csdn.net/'+user_name
            yield scrapy.http.Request(user_page_url, self.parse)          # 发送用户页面请求,响应对象的回调方法为 parse


    # 访问用户博客页面,获取博客数据(分为2种页面风格获取信息)
    def blog_parse(self, response):
        blog_item = BlogItem()

        if response.xpath('//ul[@class="panel_body profile"]'):  # 旧版博客页面
            blog_item['user_id'] = response.url.split('/')[-1]
            blog_item['user_name'] = response.xpath('//a[@class="user_name"]/text()').extract()[0]
            blog_item['visit_count'] = response.xpath('//ul[@id="blog_rank"]/li[1]/span/text()').extract()[0][:-1]
            blog_item['rank'] = response.xpath('//ul[@id="blog_rank"]/li[1]/span/text()').extract()[0][1:-1]
        else:                                                     # 新版博客页面
            blog_item['user_id'] = response.url.split('/')[-1]
            blog_item['user_name'] = response.xpath('//a[@id="uid"]/text()').extract()[0]
            blog_item['visit_count'] = ''.join(response.xpath('//div[@class="gradeAndbadge"][1]/span[@class="num"]/text()').extract()[0].split(','))
            blog_item['rank'] = response.xpath('//div[@class="gradeAndbadge"][3]/span[@class="num"]/text()').extract()[0]

        log.msg(blog_item,level=log.INFO)
        yield blog_item


运行爬虫任务

在 scrapy 爬虫项目的路径下,可以通过类似如下启动项目的某一个 spider:
scrapy crawl blog         # 启动 name = ‘blog’ 的 Spider 任务 
如果不想输出日志,可以直接通过以下:
scrapy crawl blog --nolog
如果想将 Item 数据直接保存为 json 文件,scrapy 提供了默认的实现,可以如下启动 spider:
scrapy crawl blog -o data.json


实现 Pipeline 

scrapy 数据传输的流程如下,由 Spider 从 Reponse 获取数据,进行处理后,结构化储存在 Item 中,再交给不同优先级别 Pipeline 对象对 Item 进行进一步处理,这个过程可以是异步进行的;
基于这个特性,Pipeline 特别适合用来进行数据项去重,格式化输出文件,数据库持久化;

以下在 pipelines.py 文件中实现了 4 个 Pipeline 对象,Pipeline 对象只要包含 process_item 、open_spider、close_spider 等方法即可;
# @File: pipelines.py
import json
import pymysql
from scrapy import log


# 去重过滤器
class DuplicatesPipeline(object):
    def __init__(self):
        self.id_set = set()   #内部使用一个 set() 维护数据

    def process_item(self, item, spider):
        if item['user_id'] not in self.id_set:
            self.id_set.add(item['user_id'])
            return item


# 格式化输出 Json (将每一个 item 都输出为一个 json 字符串)
class JsonWritePipeline(object):
    def __init__(self):
        self.output = open('json_format.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.output.write(line)
        return item

    def close_spider(self):
        self.output.close()


# 格式化输出为CSV文件
class CSVWritePipeline(object):
    def __init__(self):
        self.output = open('data.csv', 'w', encoding='utf-8')
        self.output.write('user_id,user_name,visit_count,rank'+"\n")

    def process_item(self, item, spider):
        line = self.__checkout(item['user_id']) + ',' + self.__checkout(item['user_name']) + ',' \
            + self.__checkout(item['visit_count']) + ',' + self.__checkout(item['rank'])
        self.output.write(line)
        return item

    def __checkout(self, src_str):
        for ch in str(src_str):
            if ch == ' ' or ch == ',':
                return '"' + src_str + '"'
        return src_str

    def close_spider(self):
        self.output.close()


# 储存到 MySQL 数据库( 使用 pymysql 模块):不考虑优化,直接实现功能
class MySQLPipeline(object):

    # 启动 spider 时,创建数据库连接对象
    def open_spider(self):
        host = "localhost"
        user = "root"
        password = "23333"
        db_name = "CSDNDB"
        try:
            self.db = pymysql.connect(host,user,password,db_name)
        except:
            log.msg("mysql connection error", level=log.ERROR)


    def process_item(self, item, spider):
        cursor = self.db.cursor()
        sql = "INSERT INTO CSDNDB(user_id,user_name,visit_count,rank) VALUES(%s,%s,%d,%d) " % \
              (item['user_id'], item['user_name'], item['visit_count'], item['rank'])
        try:
            cursor.execute(sql)
            self.db.commit()
        except:
            self.db.rollback()
        return item

    # 关闭 spider 时,销毁数据库连接对象
    def close_spider(self):
        self.db.close()

要对 Item 启用这些 pipeline ,只需要在 settings.py 文件中启用 ITEM_PIPELINES 常量即可,如下:
# 启用 Item 的 pipeline 对象组
ITEM_PIPELINES = {
    'csdn.pipelines.DuplicatesPipeline': 900,
    # 'csdb.pipelines.JsonWritePipeline': 800,
     'csdb.pipelines.CSVWritePipeline': 500,
    # 'csdb.pipelines.MySQLPipeline': 400,
}
900,800 等参数代表该 pipeline 处理 item 的优先级,数字越高优先级越大,取值 0-999;