项目总结

昨天爬虫没有取得数据,我去把settings中所有能够开的,自己知道的设置全部都开了,还是不行,老师远程调试了很久,删除了他的代码,程序又不能拿到数据。

百度查了报错,先是更改主机域名,随后在生成请求的语句中,参数设置不过滤,然后数据就被爬到了。

另外一个收获就是使用管道存储的关联流程。在settings文件中开启管道存储,在items中建立结构在piplines中导入item,然后调用item。至此关联处理完毕。

解决爬取不到数据的方法

image.png

spider类下载图片

分析请求流程

百度图片搜索关键字,在网页源代码中找到有规律的图片URL

image.png

创建scrapy项目

  1. cd C:\Users\41999\Documents\PycharmProjects\Python\TZSpyder\第九节Scrapy(六)
  2. scrapy startproject bdimage
  3. cd cd C:\Users\41999\Documents\PycharmProjects\Python\TZSpyder\第九节Scrapy(六)\b
  4. aiduimage\baiduimage
  5. cd ../
  6. start genspider bdimgspider xxx

image.png

结合代码构建请求流程

1 设定起始页URL 解析URL 获取图片的URL

2 使用re模块匹配响应网页源码中的每一个图片URL

3 for循环,遍历每一个图片的URL,并且利用生成器发起访问

4 得到响应,使用生成语句中的回调函数将响应转给指定的解析函数

image.png

settings配置参数

关闭遵循爬虫协议 增添请求头

预打印单个URL,看是否匹配成功

image.png

代码解析

第一部分

  1. import scrapy, re, os
  2. from ..items import BaiduimageItem
  3. class BdimgspiderSpider(scrapy.Spider):
  4. num = 0
  5. name = 'bdimgspider'
  6. allowed_domains = ['image.baidu.com/']
  7. start_urls = [
  8. 'https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fr=&sf=1&fmq=1567133149621_R&pv=&ic=0&nc=1&z=0&hd=0&latest=0&copyright=0&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&sid=&word=%E5%A3%81%E7%BA%B8']
  9. def parse(self, response):
  10. # 解析page页 获取图片URL
  11. img_urls = re.findall(r'"thumbURL":"(.*?)"', response.text)
  12. # 发送请求获取图片数据
  13. for index, img_url in enumerate(img_urls):
  14. yield scrapy.Request(img_url, callback=self.get_img, dont_filter=True)
  15. def get_img(self, response):
  16. print("图片数据", response.status)
  17. img_data = response.body # 图片二进制数据
  18. # 一直存储
  19. if not os.path.exists("dirspider"):
  20. os.mkdir("dirspider")
  21. filename = "dirspider/%s.jpg" % str(self.num)
  22. self.num += 1
  23. with open(filename, "wb") as f:
  24. f.write(img_data)
  25. # 使用管道存储
  26. item = BaiduimageItem()
  27. item["img_data"] = img_data
  28. yield item

首先从响应中获得网页源码,通过re模块解析网页中的指定URL,获取每一个图片的URL

通过枚举,生成器访问每一个图片的URL,并且将网站的返回数据传递给指定的解析函数

  1. def parse(self, response):
  2. # 解析page页 获取图片URL
  3. img_urls = re.findall(r'"thumbURL":"(.*?)"', response.text)
  4. # 发送请求获取图片数据
  5. for index, img_url in enumerate(img_urls):
  6. yield scrapy.Request(img_url, callback=self.get_img)

image.png

第二部分

打印获得访问每张图片的响应状态码,确认响应正常

由变量img_data接受活到的响应数据,由于图片数据是二进制文件,所以这里获得的是响应的主体文字

建立文件夹,持久化存储数据

首先使用if语句判断不存在指定文件夹,然后使用os模块分别识别是否存在以及建立指定文件夹

单个文件,使用字符串拼接文件名字,在类下的第一行添加一个变量num,初始值为1,在文件名建立成功后,使用self.name格式自增加,方式图片的名字而非属性的名字重名

打开一个文件文件,以wb方式打开,然后使用写入方法写入上面建立的变量img_data,它会逐次获得每一个图片的二进制数据

最后使用管道存储

  1. def get_img(self, response):
  2. print("图片数据", response.status)
  3. img_data = response.body # 图片二进制数据
  4. # 一直存储
  5. if not os.path.exists("dirspider"):
  6. os.mkdir("dirspider")
  7. filename = "dirspider/%s.jpg"%str(self.num)
  8. self.num += 1
  9. with open(filename, "wb") as f:
  10. f.write(img_data)

image.png

在管道存储中

在管道中创建如下代码,首先是创建一个变量num初始值为1,用于自增长

  1. # Define here the models for your scraped items
  2. #
  3. # See documentation in:
  4. # https://docs.scrapy.org/en/latest/topics/items.html
  5. import scrapy
  6. class BaiduimageItem(scrapy.Item):
  7. # define the fields for your item here like:
  8. img_data = scrapy.Field()

然后定义执行item,在item中,有如下大致的流程,

首先在items中定义存储的变量,然后去piplines下引入items下包含该变量的类,然后去设置中打开管道,这是三部基础操作,便于我们使用管道存储数据

  1. # Define your item pipelines here
  2. #
  3. # Don't forget to add your pipeline to the ITEM_PIPELINES setting
  4. # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
  5. # useful for handling different item types with a single interface
  6. from .items import BaiduimageItem
  7. import os
  8. class BaiduimagePipeline:
  9. num = 0
  10. def process_item(self, item, spider):
  11. if not os.path.exists("dirspider_pipe"):
  12. os.mkdir("dirspider_pipe")
  13. filename = "dirspider_pipe/%s.jpg" % str(self.num)
  14. self.num += 1
  15. with open(filename, "wb") as f:
  16. f.write(item["img_data"])
  17. return item

在管道的类定义中,依然是创建一个变量初始值为零,然后在名为执行itme的函数中,定义存储图片的文件夹和文件的名称

定义文件夹主要由OS模块完成,只要不存在则创建该指定文件夹

定义文件名主要由管道名称,加上字符串而来,而字符串就是对自增加数值的转换

最关键的一步,打开刚才创建的文件,并且以二进制方式写入爬取到的图片数据

  1. class BaiduimagePipeline:
  2. num = 0
  3. def process_item(self, item, spider):
  4. if not os.path.exists("dirspider_pipe"):
  5. os.mkdir("dirspider_pipe")
  6. filename = "dirspider_pipe/%s.jpg" % str(self.num)
  7. self.num += 1
  8. with open(filename, "wb") as f:
  9. f.write(item["img_data"])
  10. return item

image.png

批量爬取

第一个细节处理:关于获取有规律的图片URL
image.png

代码中的书写
image.png
要修改的URL参数
image.png
修改后的样式
image.png

设置爬取指定页数的图片

设置变量,每次for遍历请求一次则变量自增减,然后将增减的变量乘以三十返回给原来的主页面,此时又会生成下一个包含30张图片的网页地址
image.png
指定获取前四页的内容,超过四页则返回空(这是在函数里面可以这样用,在while语句中则使用break)for语句使用自增减
image.png

解决图片数量不足的问题

纠正写错的代码,生成语句中,每次被执行的page_url应该是由page_num乘法运算得出的结果

image.png

ImagesPipline类下载图片

1 在items中创建和主程序名字对应的items类,且创建存储结构

  1. class BdImagePipeItem(scrapy.Item):
  2. # define the fields for your item here like:
  3. img_urls = scrapy.Field()

image.png

2 在主程序中创建存储结构,使用的是引用加字典的方式

首先在头位置导入items包中的类BdImgPipItem,然后再主程序中使用变量的方式实例化类

实例化向其传递之前已经创建好的单个图片url

image.png

3 导入框架中的类,并且继承用于处理图片存储
image.png
4 设置媒体管道类的存储位置

注意:由于是绝对路径,同一份代码到其他主机运行需要修改它的存储路径
image.png
5 在settings中打开管道

其实就是复制上一个管道,并且修改其名字,改一下优先级
image.png

ImagesPipeline类简介

1 在主文件中从起始页发起请求,获得响应

2 正则表达式匹配每个图片的URL

3 将URL传递给items

4 pipeline收到items
在这个文件中,继承图片处理的管道类: from scrapy.pipelines.images import ImagesPipeline

从items中导入两个图片存储类: from .items import BaiduimageItem, BdImagePipeItem

定义图片管道类,并且继承图片处理管道类 : class BdImagePipeline(ImagesPipeline)
**
在settings中指定存储位置

IMAGES_STORE = r”C:\Users\41999\Documents\PycharmProjects\Python\TZ—Spyder\第九节Scrapy(六)\baiduimage\baiduimage\ImagePipedir”

ImagesPipeline类重写

实现图片管道类的代码

  1. """
  2. Images Pipeline
  3. See documentation in topics/media-pipeline.rst
  4. """
  5. import functools
  6. import hashlib
  7. from contextlib import suppress
  8. from io import BytesIO
  9. from itemadapter import ItemAdapter
  10. from PIL import Image
  11. from scrapy.exceptions import DropItem
  12. from scrapy.http import Request
  13. from scrapy.pipelines.files import FileException, FilesPipeline
  14. # TODO: from scrapy.pipelines.media import MediaPipeline
  15. from scrapy.settings import Settings
  16. from scrapy.utils.misc import md5sum
  17. from scrapy.utils.python import to_bytes
  18. class NoimagesDrop(DropItem):
  19. """Product with no images exception"""
  20. class ImageException(FileException):
  21. """General image error exception"""
  22. class ImagesPipeline(FilesPipeline):
  23. """Abstract pipeline that implement the image thumbnail generation logic
  24. """
  25. MEDIA_NAME = 'image'
  26. # Uppercase attributes kept for backward compatibility with code that subclasses
  27. # ImagesPipeline. They may be overridden by settings.
  28. MIN_WIDTH = 0
  29. MIN_HEIGHT = 0
  30. EXPIRES = 90
  31. THUMBS = {}
  32. DEFAULT_IMAGES_URLS_FIELD = 'image_urls'
  33. DEFAULT_IMAGES_RESULT_FIELD = 'images'
  34. # 解析settings里的配置字段
  35. def __init__(self, store_uri, download_func=None, settings=None):
  36. super().__init__(store_uri, settings=settings, download_func=download_func)
  37. if isinstance(settings, dict) or settings is None:
  38. settings = Settings(settings)
  39. resolve = functools.partial(self._key_for_pipe,
  40. base_class_name="ImagesPipeline",
  41. settings=settings)
  42. self.expires = settings.getint(
  43. resolve("IMAGES_EXPIRES"), self.EXPIRES
  44. )
  45. if not hasattr(self, "IMAGES_RESULT_FIELD"):
  46. self.IMAGES_RESULT_FIELD = self.DEFAULT_IMAGES_RESULT_FIELD
  47. if not hasattr(self, "IMAGES_URLS_FIELD"):
  48. self.IMAGES_URLS_FIELD = self.DEFAULT_IMAGES_URLS_FIELD
  49. self.images_urls_field = settings.get(
  50. resolve('IMAGES_URLS_FIELD'),
  51. self.IMAGES_URLS_FIELD
  52. )
  53. self.images_result_field = settings.get(
  54. resolve('IMAGES_RESULT_FIELD'),
  55. self.IMAGES_RESULT_FIELD
  56. )
  57. self.min_width = settings.getint(
  58. resolve('IMAGES_MIN_WIDTH'), self.MIN_WIDTH
  59. )
  60. self.min_height = settings.getint(
  61. resolve('IMAGES_MIN_HEIGHT'), self.MIN_HEIGHT
  62. )
  63. self.thumbs = settings.get(
  64. resolve('IMAGES_THUMBS'), self.THUMBS
  65. )
  66. @classmethod
  67. def from_settings(cls, settings):
  68. s3store = cls.STORE_SCHEMES['s3']
  69. s3store.AWS_ACCESS_KEY_ID = settings['AWS_ACCESS_KEY_ID']
  70. s3store.AWS_SECRET_ACCESS_KEY = settings['AWS_SECRET_ACCESS_KEY']
  71. s3store.AWS_ENDPOINT_URL = settings['AWS_ENDPOINT_URL']
  72. s3store.AWS_REGION_NAME = settings['AWS_REGION_NAME']
  73. s3store.AWS_USE_SSL = settings['AWS_USE_SSL']
  74. s3store.AWS_VERIFY = settings['AWS_VERIFY']
  75. s3store.POLICY = settings['IMAGES_STORE_S3_ACL']
  76. gcs_store = cls.STORE_SCHEMES['gs']
  77. gcs_store.GCS_PROJECT_ID = settings['GCS_PROJECT_ID']
  78. gcs_store.POLICY = settings['IMAGES_STORE_GCS_ACL'] or None
  79. ftp_store = cls.STORE_SCHEMES['ftp']
  80. ftp_store.FTP_USERNAME = settings['FTP_USER']
  81. ftp_store.FTP_PASSWORD = settings['FTP_PASSWORD']
  82. ftp_store.USE_ACTIVE_MODE = settings.getbool('FEED_STORAGE_FTP_ACTIVE')
  83. store_uri = settings['IMAGES_STORE']
  84. return cls(store_uri, settings=settings)
  85. def file_downloaded(self, response, request, info, *, item=None):
  86. return self.image_downloaded(response, request, info, item=item)
  87. def image_downloaded(self, response, request, info, *, item=None):
  88. checksum = None
  89. for path, image, buf in self.get_images(response, request, info, item=item):
  90. if checksum is None:
  91. buf.seek(0)
  92. checksum = md5sum(buf)
  93. width, height = image.size
  94. self.store.persist_file(
  95. path, buf, info,
  96. meta={'width': width, 'height': height},
  97. headers={'Content-Type': 'image/jpeg'})
  98. return checksum
  99. # 图片获取方法 图片大小的过滤 缩略图的生成
  100. def get_images(self, response, request, info, *, item=None):
  101. path = self.file_path(request, response=response, info=info, item=item)
  102. orig_image = Image.open(BytesIO(response.body))
  103. width, height = orig_image.size
  104. if width < self.min_width or height < self.min_height:
  105. raise ImageException("Image too small "
  106. f"({width}x{height} < "
  107. f"{self.min_width}x{self.min_height})")
  108. image, buf = self.convert_image(orig_image)
  109. yield path, image, buf
  110. for thumb_id, size in self.thumbs.items():
  111. thumb_path = self.thumb_path(request, thumb_id, response=response, info=info)
  112. thumb_image, thumb_buf = self.convert_image(image, size)
  113. yield thumb_path, thumb_image, thumb_buf
  114. # 转换图片格式
  115. def convert_image(self, image, size=None):
  116. if image.format == 'PNG' and image.mode == 'RGBA':
  117. background = Image.new('RGBA', image.size, (255, 255, 255))
  118. background.paste(image, image)
  119. image = background.convert('RGB')
  120. elif image.mode == 'P':
  121. image = image.convert("RGBA")
  122. background = Image.new('RGBA', image.size, (255, 255, 255))
  123. background.paste(image, image)
  124. image = background.convert('RGB')
  125. elif image.mode != 'RGB':
  126. image = image.convert('RGB')
  127. if size:
  128. image = image.copy()
  129. image.thumbnail(size, Image.ANTIALIAS)
  130. buf = BytesIO()
  131. image.save(buf, 'JPEG')
  132. return image, buf
  133. # 生成媒体请求
  134. def get_media_requests(self, item, info):
  135. # 得到图片URL 变成请求 发给引擎
  136. urls = ItemAdapter(item).get(self.images_urls_field, [])
  137. return [Request(u) for u in urls]
  138. def item_completed(self, results, item, info): # 此方法 提取文件名进行改写
  139. with suppress(KeyError):
  140. ItemAdapter(item)[self.images_result_field] = [x for ok, x in results if ok]
  141. return item
  142. def file_path(self, request, response=None, info=None, *, item=None):
  143. image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
  144. # URL是唯一 hash值也是唯一 图片名字是唯一的
  145. return f'full/{image_guid}.jpg'
  146. def thumb_path(self, request, thumb_id, response=None, info=None):
  147. # 缩略图的存储路径
  148. thumb_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
  149. return f'thumbs/{thumb_id}/{thumb_guid}.jpg'

自定义图片下载管道类

注意:自定义图片下载管道类的方法写在pipelines文件中
需要在items中建立对应结构,同时在settings中编写并且开启对应管道

  1. class BdImagePipeline(ImagesPipeline):
  2. pass
  3. def get_media_requests(self, item, info):
  4. # 得到图片URL 变成请求 发给引擎
  5. print('item["image_urls"]', item["image_urls"])
  6. for x in item["image_urls"]:
  7. yield scrapy.Request(x)
  8. def item_completed(self, results, item, info): # 此方法 提取文件名进行改写
  9. print("媒体管道类结果")
  10. print(results)
  11. # with suppress(KeyError):
  12. # ItemAdapter(item)[self.images_result_field] = [x for ok, x in results if ok]
  13. # return item

image.png

打印的结构内容信息

image.png

打印媒体管道类内容

分别打印出获取的URL对象,图片的存储路径,MD5加密,以及下载状态
image.png

对应文件的查找

image.png

构建代码更改文件名字

首先从响应中提取文件名,注意文件名存放的层次,从元组,列表到键值对依次深入提取

注意加文件时用的反斜杠,这个格式要和之前在settings中设置的存储绝对路径保持一致

调试代码的时候,一定要把之前的内容全部删除重新爬取

  1. class BdImagePipeline(ImagesPipeline):
  2. image_num = 0
  3. def get_media_requests(self, item, info):
  4. # 得到图片URL 变成请求 发给引擎
  5. print('item["image_urls"]', item["image_urls"])
  6. for x in item["image_urls"]:
  7. yield scrapy.Request(x)
  8. def item_completed(self, results, item, info): # 此方法 提取文件名进行改写
  9. print("媒体管道类结果")
  10. print(results)
  11. # results[0][1]["path"]
  12. for ok, x in results:
  13. if ok:
  14. print(x["path"])
  15. images_path = [x["path"] for ok, x in results if ok]
  16. for image_path in images_path:
  17. os.rename(IMAGES_STORE + "/" + image_path, IMAGES_STORE + "/" + str(self.image_num) + ".jpg")
  18. self.image_num += 1

image.png