转载Python爬虫框架Scrapy学习笔记
Posted 量化金融科技前沿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转载Python爬虫框架Scrapy学习笔记相关的知识,希望对你有一定的参考价值。
原创: 贝熊熊晚安 慕课网
本文按照以下目录来阐述
开始
scrapy安装
新建项目
`SHELL`模式
`pycharm`调试启动文件
项目基本配置
XPATH
操作例子
CSS选择器
操作例子
爬虫
爬取某网站文章列表例子
图片自动下载
图片自动下载自定义类
数据保存
把item数据导出到json文件中
使用自带的模块导出json文件
使用mysql保存
使用同步的机制写入mysql
使用异步的机制写入mysql
item loader
item loader的一般使用
配合item 的processor处理器的使用
自定义item loader
开始
scrapy安装
首先手动安装windows版本的
Twisted
安装
scrapy
pip install -i https://pypi.douban.com/simple/ scrapy
windows系统额外需要安装
pypiwin32
pip install -i https://pypi.douban.com/simple pypiwin32
新建项目
开始一个项目
E:\svnProject> scrapy startproject TestSpider
生成一个新的爬虫(generate)
E:\svnProject> cd TestSpider
E:\svnProject\TestSpider> scrapy genspider dongfeng www.dongfe.com
启动一个爬虫
E:\svnProject\TestSpider> scrapy crawl dongfeng
`SHELL`模式
> scrapy shell http://www.dongfe.com/ # 命令行调试该网页
`pycharm`调试启动文件
E:\svnProject\TestSpider> vim main.py
import sys
import os
from scrapy.cmdline import execute
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
# scrapy crawl dongfeng
execute(["scrapy", "crawl", "dongfeng"])
项目基本配置
E:\svnProject\TestSpider\TestSpider> vim settings.py
ROBOTSTXT_OBEY = False # 不要遵循网站robots文件
XPATH
表达式 | 说明 |
---|---|
/body | 选出当前选择器的根元素body |
/body/div | 选取当前选择器文档的根元素body的所有div子元素 |
/body/div[1] | 选取body根元素下面第一个div子元素 |
/body/div[last()] | 选取body根元素下面最后一个div子元素 |
/body/div[last()-1] | 选取body根元素下面倒数第二个div子元素 |
//div | 选取所有div子元素(不论出现在文档任何地方) |
body//div | 选取所有属于body元素的后代的div元素(不论出现在body下的任何地方) |
/body/@id | 选取当前选择器文档的根元素body的id属性 |
//@class | 选取所有元素的class属性 |
//div[@class] | 选取所有拥有class属性的div元素 |
//div[@class='bold'] | 选取所有class属性等于bold的div元素 |
//div[contains(@class,'bold')] | 选取所有class属性包含bold的div元素 |
/div/* | 选取当前文档根元素div的所有子元素 |
//* | 选取文档所有节点 |
//div[@*] | 获取所有带属性的div元素 |
//div/a | //div/p | 选取所有div元素下面的子元素a和子元素p(并集) |
//p[@id='content']/text() | 选取id为content的p标签的内容(子元素的标签和内容都不会获取到) |
注意: XPATH在选择时,参考的是html源码,而不是JS加载后的HTML代码
操作例子
title_selector = response.xpath("//div[@class='entry-header']/h1/text()")
title_str = title_selector.extract()[0]
CSS选择器
表达式 | 说明 |
---|---|
* | 选择所有节点 |
#container | 选择Id为container的节点 |
.container | 选取所有包含container类的节点 |
li a | 选取所有li下的所有后代a元素(子和孙等所有的都会选中) |
ul + p | 选取ul后面的第一个相邻兄弟p元素 |
div#container > ul | 选取id为container的div的所有ul子元素 |
ul ~ p | 选取与ul元素后面的所有兄弟p元素 |
a[title] | 选取所有有title属性的a元素 |
a[href='http://taobao.com'] | 选取所有href属性等于http://taobao.com的a元素 |
a[href*='taobao'] | 选取所有href属性包含taobao的a元素 |
a[href^='http'] | 选取所有href属性开头为http的a元素 |
a[href$='.com'] | 选取所有href属性结尾为.com的a元素 |
input[type=radio]:checked | 选取选中的radio的input元素 |
div:not(#container) | 选取所有id非container的div元素 |
li:nth-child(3) | 选取第三个li元素 |
tr:nth-child(2n) | 选取偶数位的tr元素 |
a::attr(href) | 获取所有a元素的href属性值 |
操作例子
h1_selector = response.css(".container h1::text") # 选取h1标题的内容
h1_str = h1_selector.extract_first() # 取出数组第一个,如果没有为空
爬虫
爬取某网站文章列表例子
>>> vim ArticleSpider/spiders/jobbole.py
import scrapy
from scrapy.http import Request
from urllib import parse
import re
from ArticleSpider.items import ArticleItem
from ArticleSpider.utils.common import get_md5 # url转md5
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
def parse(self, response):
"""
文章列表页的文章链接解析
:param response:
:return:
"""
css = "#archive > .post > .post-thumb > a"
article_urls_selector = response.css(css) # 获取当前列表页所有文章的链接
for article_url_selector in article_urls_selector:
head_img_url = article_url_selector.css("img::attr(src)").extract_first() # 封面URL
head_img_full_url = parse.urljoin(response.url, head_img_url) # 封面图片完整URL
article_url = article_url_selector.css("a::attr(href)").extract_first("") # 文章URL
article_full_url = parse.urljoin(response.url, article_url) # 智能的拼接URL,相对地址直接对接;绝对地址只取出域名对接;完全地址不对接,直接获取。
yield Request(url=article_full_url, callback=self.article_parse, meta={"head_img_full_url": head_img_full_url}) # 请求文章详情页并设置回调函数解析内容和meta传参
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) # 下一页文章列表使用递归
def article_parse(self, response):
"""
文章详情页的内容解析
:param response:
:return:
"""
title = response.css(".grid-8 .entry-header > h1::text").extract_first("") # 标题内容
add_time = response.css(".grid-8 .entry-meta p::text").extract_first("")
add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", add_time)
if add_time_match:
add_time = add_time_match.group(1)
else:
add_time = add_time.strip()
content = response.css(".grid-8 .entry").extract_first("") # 文章内容
star = response.css("h10::text").extract_first("") # 点赞数
head_img_url = response.meta.get("head_img_full_url") # 封面URL,通过上一个解释器在回调时传参得到的数据
# 把数据整理到item
article_item = ArticleItem() # 实例化一个item
article_item["title"] = title
article_item["content"] = content
# 把时间字符串转为可保存mysql的日期对象
try:
add_time = datetime.datetime.strptime(add_time, "%Y/%m/%d").date()
except Exception as e:
add_time = datetime.datetime.now().date()
article_item["add_time"] = add_time
article_item["star"] = star
article_item["head_img_url"] = [head_img_url] # 传递URL图片保存列表供ImagesPipeline使用
article_item["url"] = response.url
article_item["url_object_id"] = get_md5(response.url) # 获取url的md5值
yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典
# Item设计,类似于django的表单类
>>> vim ArticleSpider/items.py
import scrapy
class ArticleItem(scrapy.Item):
title = scrapy.Field() # 标题
content = scrapy.Field() # 内容
add_time = scrapy.Field() # 文章添加时间
url = scrapy.Field() # 文章URL
url_object_id = scrapy.Field() # URL的MD5值
head_img_url = scrapy.Field() # 封面图URL
head_img_path = scrapy.Field() # 封面图本地路径
star = scrapy.Field() # 点赞数
>>> vim ArticleSpider/spiders/settings.py
# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道
# Item管道
ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件
'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理
}
# URL转md5函数
>>> create ArticleSpider/utils/__init__.py # 公共工具包
>>> vim common.py
import hashlib
def get_md5(url): # 获取URL的MD5值
if isinstance(url, str): # 如果是Unicode字符串
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url) # 只接受UTF-8字节码
return m.hexdigest()
图片自动下载
>>> vim ArticleSpider/settings.py
PROJECT_DIR = os.path.join(BASE_DIR, "ArticleSpider")
# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道
# Item管道
ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件
'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理
'scrapy.pipelines.images.ImagesPipeline': 1,
}
IMAGES_URLS_FIELD = "head_img_url" # 图片URL的字段名
IMAGES_STORE = os.path.join(PROJECT_DIR, "images") # 图片本地保存地址
# IMAGES_MIN_HEIGHT = 100 # 接收图片的最小高度
# IMAGES_MIN_WIDTH = 100 # 接收图片的最小宽度
图片自动下载自定义类
>>> vim ArticleSpider/settings.py
ITEM_PIPELINES = {
...
#'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
}
>>> vim ArticleSpider/pipelines.py
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
if "head_img_url" in item: # 只处理有数据的URL
for ok, value in results: # 默认是多个图片URL,其实只传递了一个,所以results内只有一个
image_file_path = value["path"] # 获取图片保存的本地路径
item["head_img_path"] = image_file_path
return item # 返回item,下一个pipeline接收处理
数据保存
把item数据导出到json文件中
vim ArticleSpider/pipelines.py
import codecs # 文件操作模块
import json
# 把item保存到json文件
class JsonWithEncodingPipeline(object):
def __init__(self):
self.file = codecs.open("article.json", 'w', encoding="utf-8")
def process_item(self, item, spider):
lines = json.dumps(dict(item), ensure_ascii=False) + "\n" # 关闭ascii保存,因为有中文
self.file.write(lines)
return item
def spider_closed(self, spider):
# 当爬虫关闭时
self.file.close()
注册到item管道配置中
vim ArticleSpider/settings.py
ITEM_PIPELINES = {
'...',
'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
}
使用自带的模块导出json文件
>>> vim ArticleSpider/pipelines.py
from scrapy.exporters import JsonItemExporter
class JsonExporterPipeline(object):
# 调用scrapy提供的json export导出json文件
def __init__(self):
self.file = open("articleexport.json", "wb")
self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
self.exporter.start_exporting()
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()
注册到item管道配置中
vim ArticleSpider/settings.py
ITEM_PIPELINES = {
'...',
'ArticleSpider.pipelines.JsonExporterPipeline': 2,
}
使用mysql保存
# 安装mysql驱动
>>> pip install mysqlclient
# centos需要另外安装驱动
>>> sudo yum install python-devel mysql-devel
使用同步的机制写入mysql
import MySQLdb
class MysqlPipeline(object):
def __init__(self):
self.conn = MySQLdb.connect('dongfe.com', 'root', 'Xiong123!@#', 'article_spider', charset="utf8", use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = """
insert into article(title, url, add_time, star)
values (%s, %s, %s, %s)
"""
self.cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))
self.conn.commit()
使用异步的机制写入mysql
import MySQLdb
import MySQLdb.cursors
from twisted.enterprise import adbapi
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
pass # 这个方法会把settings文件传进来
dbparms = dict(
host="dongfe.com",
db="article_spider",
user="root",
passwd="Xiong123!@#",
charset="utf8",
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
# 使用twisted将mysql插入变成异步执行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error) # 处理异常
def do_insert(self, cursor, item):
# 执行具体的插入
insert_sql = """
insert into article(title, url, add_time, star)
values (%s, %s, %s, %s)
"""
cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))
def handle_error(self, failure, item, spider):
# 处理异步插入的异常
print(failure)
item Ioader
直接将Item
和CSS
选择器绑定到一起,直接把选择出来的数据放入Item中。
item loader的一般使用
>>> vim ArticleSpider/spiders/jobbole.py
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
...
# 文章详情页的内容解析
def article_parse(self, response):
# 通过item loader加载item
item_loader = ItemLoader(item=ArticleItem(), response=response)
item_loader.add_css("title", ".grid-8 .entry-header > h1::text")
item_loader.add_css("content", ".grid-8 .entry")
item_loader.add_css("add_time", ".grid-8 .entry-meta p::text")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_value("head_img_url", [head_img_url])
item_loader.add_css("star", "h10::text")
article_item = item_loader.load_item()
yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典
使用
Item Loader
的两个问题:
原始数据需要处理
解决办法:在Item内使用字段的处理器
不管数据有几个,获取的是一个数组
解决办法:在Item内字段处理器中使用
TakeFirst()
方法
配合item 的processor处理器的使用
>>> vim ArticleSpider/items.py
# MapCompose:可以调用多个函数依次运行
# TakeFirst: 与extract_first()函数一样,只选择数组第一个数据
# Join: 把数组用符号连接成字符串,比如Join(",")
from scrapy.loader.processors import MapCompose, TakeFirst, Join
# add_time键处理函数
def date_convert1(value):
add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", value)
if add_time_match:
add_time = add_time_match.group(1)
else:
add_time = value.strip()
return add_time
def date_convert2(value):
try:
add_time = datetime.datetime.strptime(value, "%Y/%m/%d").date()
except Exception as e:
add_time = datetime.datetime.now().date()
return add_time
class ArticleItem(scrapy.Item):
...
add_time = scrapy.Field(
input_processor=MapCompose(date_convert1, date_convert2), # 处理原始数据
output_processor=TakeFirst() # 只取数组中第一个数据
) # 文章添加时间
...
自定义item loader
可以设置默认的
default_output_processor = TakeFirst()
>>> vim ArticleSpider/items.py
from scrapy.loader import ItemLoader
class ArticleItemLoader(ItemLoader):
# 自定义item loader
default_output_processor = TakeFirst()
# 默认item处理器,什么都不做
def default_processor(value):
return value
class ArticleItem(scrapy.Item):
...
head_img_url = scrapy.Field(
# 图像URL需要一个数组类型,不取第一个数据,定义一个默认处理器覆盖掉
# 另外注意在使用sql保存时,需要取出数组第一个
output_processor=default_processor
) # 封面图URL
...
##########################################################################################
>>> vim ArticleSpider/spiders/jobbole.py
from ArticleSpider.items import ArticleItemLoader
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
...
# 文章详情页的内容解析
def article_parse(self, response):
...
# 通过item loader加载item
item_loader = ArticleItemLoader(item=ArticleItem(), response=response)
...
想要系统学习爬虫框架Scrapy,戳下图
原创: 贝熊熊晚安 慕课网 1周前
本文按照以下目录来阐述
开始
scrapy安装
新建项目
`SHELL`模式
`pycharm`调试启动文件
项目基本配置
XPATH
操作例子
CSS选择器
操作例子
爬虫
爬取某网站文章列表例子
图片自动下载
图片自动下载自定义类
数据保存
把item数据导出到json文件中
使用自带的模块导出json文件
使用mysql保存
使用同步的机制写入mysql
使用异步的机制写入mysql
item loader
item loader的一般使用
配合item 的processor处理器的使用
自定义item loader
开始
scrapy安装
首先手动安装windows版本的
Twisted
安装
scrapy
pip install -i https://pypi.douban.com/simple/ scrapy
windows系统额外需要安装
pypiwin32
pip install -i https://pypi.douban.com/simple pypiwin32
新建项目
开始一个项目
E:\svnProject> scrapy startproject TestSpider
生成一个新的爬虫(generate)
E:\svnProject> cd TestSpider
E:\svnProject\TestSpider> scrapy genspider dongfeng www.dongfe.com
启动一个爬虫
E:\svnProject\TestSpider> scrapy crawl dongfeng
`SHELL`模式
> scrapy shell http://www.dongfe.com/ # 命令行调试该网页
`pycharm`调试启动文件
E:\svnProject\TestSpider> vim main.py
import sys
import os
from scrapy.cmdline import execute
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
# scrapy crawl dongfeng
execute(["scrapy", "crawl", "dongfeng"])
项目基本配置
E:\svnProject\TestSpider\TestSpider> vim settings.py
ROBOTSTXT_OBEY = False # 不要遵循网站robots文件
XPATH
表达式 | 说明 |
---|---|
/body | 选出当前选择器的根元素body |
/body/div | 选取当前选择器文档的根元素body的所有div子元素 |
/body/div[1] | 选取body根元素下面第一个div子元素 |
/body/div[last()] | 选取body根元素下面最后一个div子元素 |
/body/div[last()-1] | 选取body根元素下面倒数第二个div子元素 |
//div | 选取所有div子元素(不论出现在文档任何地方) |
body//div | 选取所有属于body元素的后代的div元素(不论出现在body下的任何地方) |
/body/@id | 选取当前选择器文档的根元素body的id属性 |
//@class | 选取所有元素的class属性 |
//div[@class] | 选取所有拥有class属性的div元素 |
//div[@class='bold'] | 选取所有class属性等于bold的div元素 |
//div[contains(@class,'bold')] | 选取所有class属性包含bold的div元素 |
/div/* | 选取当前文档根元素div的所有子元素 |
//* | 选取文档所有节点 |
//div[@*] | 获取所有带属性的div元素 |
//div/a | //div/p | 选取所有div元素下面的子元素a和子元素p(并集) |
//p[@id='content']/text() | 选取id为content的p标签的内容(子元素的标签和内容都不会获取到) |
注意: XPATH在选择时,参考的是HTML源码,而不是JS加载后的HTML代码
操作例子
title_selector = response.xpath("//div[@class='entry-header']/h1/text()")
title_str = title_selector.extract()[0]
CSS选择器
表达式 | 说明 |
---|---|
* | 选择所有节点 |
#container | 选择Id为container的节点 |
.container | 选取所有包含container类的节点 |
li a | 选取所有li下的所有后代a元素(子和孙等所有的都会选中) |
ul + p | 选取ul后面的第一个相邻兄弟p元素 |
div#container > ul | 选取id为container的div的所有ul子元素 |
ul ~ p | 选取与ul元素后面的所有兄弟p元素 |
a[title] | 选取所有有title属性的a元素 |
a[href='http://taobao.com'] | 选取所有href属性等于http://taobao.com的a元素 |
a[href*='taobao'] | 选取所有href属性包含taobao的a元素 |
a[href^='http'] | 选取所有href属性开头为http的a元素 |
a[href$='.com'] | 选取所有href属性结尾为.com的a元素 |
input[type=radio]:checked | 选取选中的radio的input元素 |
div:not(#container) | 选取所有id非container的div元素 |
li:nth-child(3) | 选取第三个li元素 |
tr:nth-child(2n) | 选取偶数位的tr元素 |
a::attr(href) | 获取所有a元素的href属性值 |
操作例子
h1_selector = response.css(".container h1::text") # 选取h1标题的内容
h1_str = h1_selector.extract_first() # 取出数组第一个,如果没有为空
爬虫
爬取某网站文章列表例子
>>> vim ArticleSpider/spiders/jobbole.py
import scrapy
from scrapy.http import Request
from urllib import parse
import re
from ArticleSpider.items import ArticleItem
from ArticleSpider.utils.common import get_md5 # url转md5
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
def parse(self, response):
"""
文章列表页的文章链接解析
:param response:
:return:
"""
css = "#archive > .post > .post-thumb > a"
article_urls_selector = response.css(css) # 获取当前列表页所有文章的链接
for article_url_selector in article_urls_selector:
head_img_url = article_url_selector.css("img::attr(src)").extract_first() # 封面URL
head_img_full_url = parse.urljoin(response.url, head_img_url) # 封面图片完整URL
article_url = article_url_selector.css("a::attr(href)").extract_first("") # 文章URL
article_full_url = parse.urljoin(response.url, article_url) # 智能的拼接URL,相对地址直接对接;绝对地址只取出域名对接;完全地址不对接,直接获取。
yield Request(url=article_full_url, callback=self.article_parse, meta={"head_img_full_url": head_img_full_url}) # 请求文章详情页并设置回调函数解析内容和meta传参
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) # 下一页文章列表使用递归
def article_parse(self, response):
"""
文章详情页的内容解析
:param response:
:return:
"""
title = response.css(".grid-8 .entry-header > h1::text").extract_first("") # 标题内容
add_time = response.css(".grid-8 .entry-meta p::text").extract_first("")
add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", add_time)
if add_time_match:
add_time = add_time_match.group(1)
else:
add_time = add_time.strip()
content = response.css(".grid-8 .entry").extract_first("") # 文章内容
star = response.css("h10::text").extract_first("") # 点赞数
head_img_url = response.meta.get("head_img_full_url") # 封面URL,通过上一个解释器在回调时传参得到的数据
# 把数据整理到item
article_item = ArticleItem() # 实例化一个item
article_item["title"] = title
article_item["content"] = content
# 把时间字符串转为可保存mysql的日期对象
try:
add_time = datetime.datetime.strptime(add_time, "%Y/%m/%d").date()
except Exception as e:
add_time = datetime.datetime.now().date()
article_item["add_time"] = add_time
article_item["star"] = star
article_item["head_img_url"] = [head_img_url] # 传递URL图片保存列表供ImagesPipeline使用
article_item["url"] = response.url
article_item["url_object_id"] = get_md5(response.url) # 获取url的md5值
yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典
# Item设计,类似于django的表单类
>>> vim ArticleSpider/items.py
import scrapy
class ArticleItem(scrapy.Item):
title = scrapy.Field() # 标题
content = scrapy.Field() # 内容
add_time = scrapy.Field() # 文章添加时间
url = scrapy.Field() # 文章URL
url_object_id = scrapy.Field() # URL的MD5值
head_img_url = scrapy.Field() # 封面图URL
head_img_path = scrapy.Field() # 封面图本地路径
star = scrapy.Field() # 点赞数
>>> vim ArticleSpider/spiders/settings.py
# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道
# Item管道
ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件
'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理
}
# URL转md5函数
>>> create ArticleSpider/utils/__init__.py # 公共工具包
>>> vim common.py
import hashlib
def get_md5(url): # 获取URL的MD5值
if isinstance(url, str): # 如果是Unicode字符串
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url) # 只接受UTF-8字节码
return m.hexdigest()
图片自动下载
>>> vim ArticleSpider/settings.py
PROJECT_DIR = os.path.join(BASE_DIR, "ArticleSpider")
# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道
# Item管道
ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件
'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理
'scrapy.pipelines.images.ImagesPipeline': 1,
}
IMAGES_URLS_FIELD = "head_img_url" # 图片URL的字段名
IMAGES_STORE = os.path.join(PROJECT_DIR, "images") # 图片本地保存地址
# IMAGES_MIN_HEIGHT = 100 # 接收图片的最小高度
# IMAGES_MIN_WIDTH = 100 # 接收图片的最小宽度
图片自动下载自定义类
>>> vim ArticleSpider/settings.py
ITEM_PIPELINES = {
...
#'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline': 1,
}
>>> vim ArticleSpider/pipelines.py
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
if "head_img_url" in item: # 只处理有数据的URL
for ok, value in results: # 默认是多个图片URL,其实只传递了一个,所以results内只有一个
image_file_path = value["path"] # 获取图片保存的本地路径
item["head_img_path"] = image_file_path
return item # 返回item,下一个pipeline接收处理
数据保存
把item数据导出到json文件中
vim ArticleSpider/pipelines.py
import codecs # 文件操作模块
import json
# 把item保存到json文件
class JsonWithEncodingPipeline(object):
def __init__(self):
self.file = codecs.open("article.json", 'w', encoding="utf-8")
def process_item(self, item, spider):
lines = json.dumps(dict(item), ensure_ascii=False) + "\n" # 关闭ascii保存,因为有中文
self.file.write(lines)
return item
def spider_closed(self, spider):
# 当爬虫关闭时
self.file.close()
注册到item管道配置中
vim ArticleSpider/settings.py
ITEM_PIPELINES = {
'...',
'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
}
使用自带的模块导出json文件
>>> vim ArticleSpider/pipelines.py
from scrapy.exporters import JsonItemExporter
class JsonExporterPipeline(object):
# 调用scrapy提供的json export导出json文件
def __init__(self):
self.file = open("articleexport.json", "wb")
self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
self.exporter.start_exporting()
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()
注册到item管道配置中
vim ArticleSpider/settings.py
ITEM_PIPELINES = {
'...',
'ArticleSpider.pipelines.JsonExporterPipeline': 2,
}
使用mysql保存
# 安装mysql驱动
>>> pip install mysqlclient
# centos需要另外安装驱动
>>> sudo yum install python-devel mysql-devel
使用同步的机制写入mysql
import MySQLdb
class MysqlPipeline(object):
def __init__(self):
self.conn = MySQLdb.connect('dongfe.com', 'root', 'Xiong123!@#', 'article_spider', charset="utf8", use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = """
insert into article(title, url, add_time, star)
values (%s, %s, %s, %s)
"""
self.cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))
self.conn.commit()
使用异步的机制写入mysql
import MySQLdb
import MySQLdb.cursors
from twisted.enterprise import adbapi
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
pass # 这个方法会把settings文件传进来
dbparms = dict(
host="dongfe.com",
db="article_spider",
user="root",
passwd="Xiong123!@#",
charset="utf8",
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
# 使用twisted将mysql插入变成异步执行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error) # 处理异常
def do_insert(self, cursor, item):
# 执行具体的插入
insert_sql = """
insert into article(title, url, add_time, star)
values (%s, %s, %s, %s)
"""
cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))
def handle_error(self, failure, item, spider):
# 处理异步插入的异常
print(failure)
item Ioader
直接将Item
和CSS
选择器绑定到一起,直接把选择出来的数据放入Item中。
item loader的一般使用
>>> vim ArticleSpider/spiders/jobbole.py
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
...
# 文章详情页的内容解析
def article_parse(self, response):
# 通过item loader加载item
item_loader = ItemLoader(item=ArticleItem(), response=response)
item_loader.add_css("title", ".grid-8 .entry-header > h1::text")
item_loader.add_css("content", ".grid-8 .entry")
item_loader.add_css("add_time", ".grid-8 .entry-meta p::text")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_value("head_img_url", [head_img_url])
item_loader.add_css("star", "h10::text")
article_item = item_loader.load_item()
yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典
使用
Item Loader
的两个问题:
原始数据需要处理
解决办法:在Item内使用字段的处理器
不管数据有几个,获取的是一个数组
解决办法:在Item内字段处理器中使用
TakeFirst()
方法
配合item 的processor处理器的使用
>>> vim ArticleSpider/items.py
# MapCompose:可以调用多个函数依次运行
# TakeFirst: 与extract_first()函数一样,只选择数组第一个数据
# Join: 把数组用符号连接成字符串,比如Join(",")
from scrapy.loader.processors import MapCompose, TakeFirst, Join
# add_time键处理函数
def date_convert1(value):
add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", value)
if add_time_match:
add_time = add_time_match.group(1)
else:
add_time = value.strip()
return add_time
def date_convert2(value):
try:
add_time = datetime.datetime.strptime(value, "%Y/%m/%d").date()
except Exception as e:
add_time = datetime.datetime.now().date()
return add_time
class ArticleItem(scrapy.Item):
...
add_time = scrapy.Field(
input_processor=MapCompose(date_convert1, date_convert2), # 处理原始数据
output_processor=TakeFirst() # 只取数组中第一个数据
) # 文章添加时间
...
自定义item loader
可以设置默认的
default_output_processor = TakeFirst()
>>> vim ArticleSpider/items.py
from scrapy.loader import ItemLoader
class ArticleItemLoader(ItemLoader):
# 自定义item loader
default_output_processor = TakeFirst()
# 默认item处理器,什么都不做
def default_processor(value):
return value
class ArticleItem(scrapy.Item):
...
head_img_url = scrapy.Field(
# 图像URL需要一个数组类型,不取第一个数据,定义一个默认处理器覆盖掉
# 另外注意在使用sql保存时,需要取出数组第一个
output_processor=default_processor
) # 封面图URL
...
##########################################################################################
>>> vim ArticleSpider/spiders/jobbole.py
from ArticleSpider.items import ArticleItemLoader
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
...
# 文章详情页的内容解析
def article_parse(self, response):
...
# 通过item loader加载item
item_loader = ArticleItemLoader(item=ArticleItem(), response=response)
...
想要系统学习爬虫框架Scrapy,戳下图
以上是关于转载Python爬虫框架Scrapy学习笔记的主要内容,如果未能解决你的问题,请参考以下文章