Scrapy 快速使用
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中;
Scrapy 官方文档:https://doc.scrapy.org/en/latest/ 不完全中文文档:
http://scrapy-chs.readthedocs.io/zh_CN/1.0/
以下以抓取 CSDN 用户博客数据为例介绍 Scrapy 的快速使用,爬取思路如下:
爬取 CSDN 的用户博客的信息,可在
http://blog.csdn.net
/user_id
页面上获取用户博客信息,通过 http://my.csdn.net/user_id 上用户的关注,被关注列表获取其他的用户;
下载安装 Scrapy
可以直接使用 pip 工具下载安装:
pip installl scrapy
下载安装出现异常参见:
https://www.cnblogs.com/liuliliuli2017/p/6746440.html
初始化项目
这里直接使用官方提供的工具初始化项目,在工作目录下:
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;