Item

Item定义

它让我们的数据面向对象,这个文件在scrapy初始化项目的时候就有

  1. class CnblogItem(scrapy.Item):
  2. title = scrapy.Field()
  3. create_date = scrapy.Field()
  4. content = scrapy.Field()
  5. tag_list = scrapy.Field()
  6. tag = scrapy.Field()
  7. totalView = scrapy.Field()
  8. front_image_url = scrapy.Field()
  9. pass

Item使用

from ``item的父目录.items ``import ``类名 导包
1.索引方式

  1. # 传递给Item
  2. CnblogItemEntity = CnblogItem()
  3. CnblogItemEntity["create_date"] = create_date
  4. CnblogItemEntity["content"] = content
  5. CnblogItemEntity["title"] = title
  6. CnblogItemEntity["tag_list"] = tag_list
  7. CnblogItemEntity["tag"] = tag
  8. CnblogItemEntity["totalView"] = totalView
  9. CnblogItemEntity["url"] = response.url
  10. CnblogItemEntity["front_image_url"] = [response.meta.get("front_image_url", "")]

2.get方式
get有什么优点,get可以防空指针,后面一个双引号就是默认值,下面案例把默认值设置为了空字符串

  1. CnblogItemEntity.get("create_date","")

url缩减成md5 - hashlib

1.创建一个utils包和spiders同级
2.创建一个文件common

  1. import hashlib
  2. def get_md5(url):
  3. if isinstance(url,str):
  4. url = url.encode("utf-8")
  5. m = hashlib.md5()
  6. m.update(url)
  7. return m.hexdigest()
  8. if __name__ == "__main__":
  9. print(get_md5("www.baidu.com"))
  10. # 结果:dab19e82e1f9a681ee73346d3e7a575e

两大协议(pip,robots)

robot

这个协议是网站的一个规约,网站会在robots.txt中告知有哪些是不允许爬的,我们这设置为True就是遵守规约,就会自动略过这些规约目录
我们通过 域名/robots.txt 就可以看到这些规约,但我们一般把它关掉。
image.png
博客园规约是全都可爬
image.png
慕课网规约有部分不可爬
image.png

pipeline

pipeline是设置管道方法的优先级的,也就是pipelines里的函数,数字越小优先级越高
image.png

怎样下载图片

配置1 - setting

image.png
IMAGES_URLS_FIELD 这个必须对应到Item中的key,key对应的value是一个图片链接,因为程序结束我们yield出来的是一个Item,配置完以后,程序运行结束的时候就会帮我们去下载图片,并保存到IMAGES_STORE中定义文件夹中

  1. IMAGES_URLS_FIELD = "front_image_url"
  2. project_dir = sys.path.append(os.path.dirname(__file__))
  3. IMAGES_STORE = os.path.join(project_dir, "images")

配置2 - pillow

  1. 下载pillow
  2. pip install -i https://pypi.douban.com/simple pillow

注意

图片的路径一定要是有中括号括起来的,代表它是一个数组,这样图片才能正常下载
接下来运行程序就可以在指定目录下载图片了。

  1. CnblogItemEntity["front_image_url"] = [response.meta.get("front_image_url", "")]

下载图片到本地

光下载图片不行,我们还得把图片的路径保存起来,数据保存都在pipeline进行操作

配置管道

配置管道都是两步走,1.在pipeline中书写代码 2.在setting中配置管道以及优先级(让python知道你配置的管道)

1.书写管道逻辑代码

  1. class ArticleImagePipeline(ImagesPipeline):
  2. def item_completed(self, results, item, info):
  3. image_file_path = ''
  4. if "front_image_url" in item:
  5. for ok, value in results:
  6. image_file_path = value["path"]
  7. item["front_image_path"] = image_file_path
  8. return item

在setting中配置管道

  1. ITEM_PIPELINES = {
  2. 'cnblog.pipelines.CnblogPipeline': 300,
  3. 'cnblog.pipelines.ArticleImagePipeline': 1 #配置这条语句
  4. }

Json格式导出数据(方式一)

process_item名字一定不要变,w是覆盖写,a是追加写。
dump方法 将dict类型转字符串类型(方便写入文件)
导包
import ``codecs
import ``json

  1. class JsonWithEncodingPipeline(object):
  2. # 自定义Json文件的导出
  3. def __init__(self):
  4. self.file = codecs.open("article.json", "w", encoding="utf-8")
  5. def process_item(self,item,spider):
  6. lines = json.dumps(dict(item), ensure_ascii=False) + "\n"
  7. self.file.write(lines)
  8. return item
  9. def spider_closed(self, spider):
  10. self.file.close()

Json格式导出数据(方式二)

导包 from ``scrapy.exporters ``import ``JsonItemExporter

  1. class JsonExporterPipeline(object):
  2. def __init__(self):
  3. self.file = open('articleExport.json', 'wb')
  4. self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
  5. self.exporter.start_exporting()
  6. def process_item(self, item, spider):
  7. self.exporter.export_item(item)
  8. return item
  9. def spider_closed(self, spider):
  10. self.exporter.finish_exporting()
  11. self.file.close()

导入数据库

将爬取的数据导入数据库
image.png

建表(表名:article)

image.png

需要导入的包

pymysql

详细剖析步骤

  1. # 第一步 获得数据库连接
  2. self.conn = pymysql.connect("127.0.0.1", 'root', 'zxc123456', "article_spider", charset='utf8',
  3. # 第二步 将管道的链接赋值给指针
  4. self.cursor = self.conn.cursor
  5. # 第三步 写sql语句,参数部分用占位符代替
  6. 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`)
  7. VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
  8. # 第四步 给占位符赋值 最后匹配sql要转元组
  9. params = list()
  10. params.append(item.get("title", ""))
  11. params.append(item.get("url", ""))
  12. params.append(item.get("url_object_id", ""))
  13. params.append(item.get("front_image_path", ""))
  14. front_image = ",".join(item.get("front_image_url", []))
  15. params.append(front_image)
  16. params.append(item.get("praise_nums", 0))
  17. params.append(item.get("comment_nums", 0))
  18. params.append(item.get("fav_nums", 0))
  19. params.append(item.get("tags", ""))
  20. params.append(item.get("content", ""))
  21. params.append(item.get("create_date", "1970-07-01"))
  22. self.cursor.execute(insert_sql, tuple(params))
  23. self.conn.commit()

完整案例

相当于把详细剖析的代码放进了管道的这种固定结构里,最后返回item是为了丢出去给其它管道接着使用。至于为什么要把数据库导入的这样一个操作放在管道中,是因为管道就是有做数据保存的这个事的。

  1. class MysqlPipeline(object):
  2. def __init__(self):
  3. self.conn = pymysql.connect("127.0.0.1", 'root', 'zxc123456', "article_spider", charset='utf8',
  4. use_unicode=True)
  5. self.cursor = self.conn.cursor()
  6. def process_item(self, item, spider):
  7. 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`)
  8. VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
  9. params = list()
  10. params.append(item.get("title", ""))
  11. params.append(item.get("url", ""))
  12. params.append(item.get("url_object_id", ""))
  13. params.append(item.get("front_image_path", ""))
  14. front_image = ",".join(item.get("front_image_url", []))
  15. params.append(front_image)
  16. params.append(item.get("praise_nums", 0))
  17. params.append(item.get("comment_nums", 0))
  18. params.append(item.get("fav_nums", 0))
  19. params.append(item.get("tags", ""))
  20. params.append(item.get("content", ""))
  21. params.append(item.get("create_date", "1970-07-01"))
  22. self.cursor.execute(insert_sql, tuple(params))
  23. self.conn.commit()
  24. return item

配置管道

管道配置两步走,第一步书写逻辑代码,第二步在setting中配置管道
image.png

异步方式导入数据库(异步方式)

导包:

twisted包(scrapy本质上能实现异步是因为twisted模块)
from ``twisted.enterprise ``import ``adbapi

第一步:配置setting

  1. MYSQL_HOST = "127.0.0.1"
  2. MYSQL_DBNAME = "article_spider"
  3. MYSQL_USER = "root"
  4. MYSQL_PASSWORD = "zxc123456"

第二步:逻辑代码

  1. class MysqlTwistedPipeline(object):
  2. def __init__(self, dbpool):
  3. self.dbpool = dbpool
  4. @classmethod
  5. def from_settings(cls, settings):
  6. dbparms = dict(
  7. host=settings["MYSQL_HOST"],
  8. db=settings["MYSQL_DBNAME"],
  9. user=settings["MYSQL_USER"],
  10. password=settings["MYSQL_PASSWORD"],
  11. charset='utf8',
  12. cursorclass=pymysql.cursors.DictCursor,
  13. use_unicode=True
  14. )
  15. dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
  16. return cls(dbpool)
  17. def process_item(self, item, spider):
  18. query = self.dbpool.runInteraction(self.do_insert, item)
  19. query.addErrback(self.handle_error, item, spider)
  20. def handle_error(self, failure, item, spider):
  21. print(failure)
  22. def do_insert(self, cursor, item):
  23. 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`)
  24. VALUES (%s , %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
  25. params = list()
  26. params.append(item.get("title", ""))
  27. params.append(item.get("url", ""))
  28. params.append(item.get("url_object_id", ""))
  29. params.append(item.get("front_image_path", ""))
  30. front_image = ",".join(item.get("front_image_url", []))
  31. params.append(front_image)
  32. params.append(item.get("praise_nums", 0))
  33. params.append(item.get("comment_nums", 0))
  34. params.append(item.get("fav_nums", 0))
  35. params.append(item.get("tags", ""))
  36. params.append(item.get("content", ""))
  37. params.append(item.get("create_date", "1970-07-01"))
  38. 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)"""