Item
Item定义
它让我们的数据面向对象,这个文件在scrapy初始化项目的时候就有
class CnblogItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field()
content = scrapy.Field()
tag_list = scrapy.Field()
tag = scrapy.Field()
totalView = scrapy.Field()
front_image_url = scrapy.Field()
pass
Item使用
from ``item的父目录.items ``import ``类名
导包
1.索引方式
# 传递给Item
CnblogItemEntity = CnblogItem()
CnblogItemEntity["create_date"] = create_date
CnblogItemEntity["content"] = content
CnblogItemEntity["title"] = title
CnblogItemEntity["tag_list"] = tag_list
CnblogItemEntity["tag"] = tag
CnblogItemEntity["totalView"] = totalView
CnblogItemEntity["url"] = response.url
CnblogItemEntity["front_image_url"] = [response.meta.get("front_image_url", "")]
2.get方式
get有什么优点,get可以防空指针,后面一个双引号就是默认值,下面案例把默认值设置为了空字符串
CnblogItemEntity.get("create_date","")
url缩减成md5 - hashlib
1.创建一个utils包和spiders同级
2.创建一个文件common
import hashlib
def get_md5(url):
if isinstance(url,str):
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url)
return m.hexdigest()
if __name__ == "__main__":
print(get_md5("www.baidu.com"))
# 结果:dab19e82e1f9a681ee73346d3e7a575e
两大协议(pip,robots)
robot
这个协议是网站的一个规约,网站会在robots.txt中告知有哪些是不允许爬的,我们这设置为True就是遵守规约,就会自动略过这些规约目录
我们通过 域名/robots.txt
就可以看到这些规约,但我们一般把它关掉。
博客园规约是全都可爬
慕课网规约有部分不可爬
pipeline
pipeline是设置管道方法的优先级的,也就是pipelines里的函数,数字越小优先级越高
怎样下载图片
配置1 - setting
IMAGES_URLS_FIELD 这个必须对应到Item中的key,key对应的value是一个图片链接,因为程序结束我们yield出来的是一个Item,配置完以后,程序运行结束的时候就会帮我们去下载图片,并保存到IMAGES_STORE中定义文件夹中
IMAGES_URLS_FIELD = "front_image_url"
project_dir = sys.path.append(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir, "images")
配置2 - pillow
下载pillow
pip install -i https://pypi.douban.com/simple pillow
注意
图片的路径一定要是有中括号括起来的,代表它是一个数组,这样图片才能正常下载
接下来运行程序就可以在指定目录下载图片了。
CnblogItemEntity["front_image_url"] = [response.meta.get("front_image_url", "")]
下载图片到本地
光下载图片不行,我们还得把图片的路径保存起来,数据保存都在pipeline进行操作
配置管道
配置管道都是两步走,1.在pipeline中书写代码 2.在setting中配置管道以及优先级(让python知道你配置的管道)
1.书写管道逻辑代码
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
image_file_path = ''
if "front_image_url" in item:
for ok, value in results:
image_file_path = value["path"]
item["front_image_path"] = image_file_path
return item
在setting中配置管道
ITEM_PIPELINES = {
'cnblog.pipelines.CnblogPipeline': 300,
'cnblog.pipelines.ArticleImagePipeline': 1 #配置这条语句
}
Json格式导出数据(方式一)
process_item名字一定不要变,w是覆盖写,a是追加写。
dump方法 将dict类型转字符串类型(方便写入文件)
导包 import ``codecs
import ``json
class JsonWithEncodingPipeline(object):
# 自定义Json文件的导出
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"
self.file.write(lines)
return item
def spider_closed(self, spider):
self.file.close()
Json格式导出数据(方式二)
导包 from ``scrapy.exporters ``import ``JsonItemExporter
class JsonExporterPipeline(object):
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 spider_closed(self, spider):
self.exporter.finish_exporting()
self.file.close()
导入数据库
建表(表名:article)
需要导入的包
详细剖析步骤
# 第一步 获得数据库连接
self.conn = pymysql.connect("127.0.0.1", 'root', 'zxc123456', "article_spider", charset='utf8',
# 第二步 将管道的链接赋值给指针
self.cursor = self.conn.cursor
# 第三步 写sql语句,参数部分用占位符代替
insert_sql = """INSERT INTO `article_spider`.`article`(`url_object_id`, `title`, `url`, `front_image_path`, `front_image_url`, `parise_nums`, `comment_nums`, `fav_nums`, `tags`, `content`, `create_date`)
VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
# 第四步 给占位符赋值 最后匹配sql要转元组
params = list()
params.append(item.get("title", ""))
params.append(item.get("url", ""))
params.append(item.get("url_object_id", ""))
params.append(item.get("front_image_path", ""))
front_image = ",".join(item.get("front_image_url", []))
params.append(front_image)
params.append(item.get("praise_nums", 0))
params.append(item.get("comment_nums", 0))
params.append(item.get("fav_nums", 0))
params.append(item.get("tags", ""))
params.append(item.get("content", ""))
params.append(item.get("create_date", "1970-07-01"))
self.cursor.execute(insert_sql, tuple(params))
self.conn.commit()
完整案例
相当于把详细剖析的代码放进了管道的这种固定结构里,最后返回item是为了丢出去给其它管道接着使用。至于为什么要把数据库导入的这样一个操作放在管道中,是因为管道就是有做数据保存的这个事的。
class MysqlPipeline(object):
def __init__(self):
self.conn = pymysql.connect("127.0.0.1", 'root', 'zxc123456', "article_spider", charset='utf8',
use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = """INSERT INTO `article_spider`.`article`(`url_object_id`, `title`, `url`, `front_image_path`, `front_image_url`, `parise_nums`, `comment_nums`, `fav_nums`, `tags`, `content`, `create_date`)
VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
params = list()
params.append(item.get("title", ""))
params.append(item.get("url", ""))
params.append(item.get("url_object_id", ""))
params.append(item.get("front_image_path", ""))
front_image = ",".join(item.get("front_image_url", []))
params.append(front_image)
params.append(item.get("praise_nums", 0))
params.append(item.get("comment_nums", 0))
params.append(item.get("fav_nums", 0))
params.append(item.get("tags", ""))
params.append(item.get("content", ""))
params.append(item.get("create_date", "1970-07-01"))
self.cursor.execute(insert_sql, tuple(params))
self.conn.commit()
return item
配置管道
管道配置两步走,第一步书写逻辑代码,第二步在setting中配置管道
异步方式导入数据库(异步方式)
导包:
twisted包(scrapy本质上能实现异步是因为twisted模块)from ``twisted.enterprise ``import ``adbapi
第一步:配置setting
MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "zxc123456"
第二步:逻辑代码
class MysqlTwistedPipeline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host=settings["MYSQL_HOST"],
db=settings["MYSQL_DBNAME"],
user=settings["MYSQL_USER"],
password=settings["MYSQL_PASSWORD"],
charset='utf8',
cursorclass=pymysql.cursors.DictCursor,
use_unicode=True
)
dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error, item, spider)
def handle_error(self, failure, item, spider):
print(failure)
def do_insert(self, cursor, item):
insert_sql = """INSERT INTO `article_spider`.`article`(`url_object_id`, `title`, `url`, `front_image_path`, `front_image_url`, `parise_nums`, `comment_nums`, `fav_nums`, `tags`, `content`, `create_date`)
VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
params = list()
params.append(item.get("title", ""))
params.append(item.get("url", ""))
params.append(item.get("url_object_id", ""))
params.append(item.get("front_image_path", ""))
front_image = ",".join(item.get("front_image_url", []))
params.append(front_image)
params.append(item.get("praise_nums", 0))
params.append(item.get("comment_nums", 0))
params.append(item.get("fav_nums", 0))
params.append(item.get("tags", ""))
params.append(item.get("content", ""))
params.append(item.get("create_date", "1970-07-01"))
cursor.execute(insert_sql, tuple(params))
第三步 配置管道
在setting文件中进行管道配置
主键重复
主键相同会报重复,但是我们在应用爬虫的时候,需要进行更新数据的操作,那更新的数据如果没有变化,就会造成数据库主键重复,我们通过sql语句特判来实现“当目标数据不变,也一样能更新数据”,下面这个语句的意思就是当主键重复的时候,采用更新操作,更新的是点赞数这个参数,点赞数采用原记录的参数
一步到位
在插入语句后加上on DUPLICATE KEY UPDATE PARISE_NUMS = VALUES(PARISE_NUMS)"""
insert_sql = """INSERT INTO `article_spider`.`article`(`url_object_id`, `title`, `url`, `front_image_path`, `front_image_url`, `parise_nums`, `comment_nums`, `fav_nums`, `tags`, `content`, `create_date`)
VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) on DUPLICATE KEY UPDATE PARISE_NUMS = VALUES(PARISE_NUMS)"""