前言
本文将介绍如何使用scrapy框架来快速爬取某网站汽车的图片,并将爬取到的图片保存到本地。
爬取的网址
https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66.html#pvareaid=2042194
创建scrapy项目
创建scrapy项目的命令在此不在赘述了,如果不清楚的小伙伴可以看下:Scrapy框架快速入门,以糗事百科为例进行说明【python爬虫入门进阶】(16) 通过如下命令创建了一个名为bba_img_demo的scrapy项目,并创建一个名为bba3的spider。
代码语言:javascript复制scrapy startproject bba_img_demo
cd bba_img_demo
scrapy genspider bba3 "car.autohome.com.cn"
爬取图片bba3Spider
这里还是采用xpath来爬取页面元素。在bba3Spider中爬取页面元素得到imgDemoItem,并返回给Pipelines。
代码语言:javascript复制class bba3Spider(scrapy.Spider):
name = 'bba3'
allowed_domains = ['car.autohome.com.cn']
start_urls = ['https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66.html#pvareaid=2042214']
def parse(self, response):
# 获取所有类别
uiboxs = response.xpath("//div[@class='uibox']")[1:]
for uibox in uiboxs:
# 获取某个类别的名称
category = uibox.xpath(".//div[@class='uibox-title']/a/text()").get()
# 获取图片链接
org_urls = uibox.xpath(".//ul/li/a/img/@src").getall()
urls = []
for url in org_urls:
# 将图片地址拼接上域名
url = response.urljoin(url)
urls.append(url)
imgDemoItem = bbaImgDemoItem(category=category, urls=urls)
yield imgDemoItem
这里需要说明的是org_urls获取到的结果是:
代码语言:javascript复制['//car2.autoimg.cn/cardfs/product/g27/M0B/8C/16/480x360_0_q95_c42_autohomecar__ChxkmWGegTyACi-kAC3FbBHMbU0705.jpg', '//car3.autoimg.cn/cardfs/product/g28/M04/89/E4/480x360_0_q95_c42_autohomecar__ChxkmmGegTuAMP6mABxWnAoC-D4144.jpg', '//car3.autoimg.cn/cardfs/product/g28/M03/89/E4/480x360_0_q95_c42_autohomecar__ChxkmmGegTmAcgvzABhIjSB7-9g758.jpg', '//car3.autoimg.cn/cardfs/product/g28/M00/89/E4/480x360_0_q95_c42_autohomecar__ChxkmmGegTmAERL1ACm2BVXLZ6Y692.jpg', '//car3.autoimg.cn/cardfs/product/g27/M04/8C/16/480x360_0_q95_c42_autohomecar__ChxkmWGegTiAXmudADZcJBeHRXE488.jpg', '//car3.autoimg.cn/cardfs/product/g27/M0A/AE/14/480x360_0_q95_c42_autohomecar__ChwFkWGegTeAU296ACGsSse0_UY335.jpg', '//car3.autoimg.cn/cardfs/product/g27/M01/8C/16/480x360_0_q95_c42_autohomecar__ChxkmWGegTaAJjHFACpeMVzBTpk896.jpg', '//car2.autoimg.cn/cardfs/product/g27/M01/AE/14/480x360_0_q95_c42_autohomecar__ChwFkWGegTWAGp1RACY4LcxwCrk373.jpg']
所以需要在爬取的图片地址前面拼接:https:
,这里既可以使用 url='https:' url
,也可以使用response.urljoin(url)
。
保存图片bbaImgDemoPipeline
在bbaImgDemoPipeline中通过接收bba3Spider返回的imgDemoItem。并将图片保存到 bba_img_demo项目的images目录下。并且在images目录下以类别来保存各个类别下的图片数据。
代码语言:javascript复制import os
from urllib import request
import ssl
from urllib.request import HTTPSHandler
context = ssl._create_unverified_context()
https_handler = HTTPSHandler(context=context)
opener = request.build_opener(https_handler)
request.install_opener(opener)
class bbaImgDemoPipeline:
def __init__(self):
self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'image')
if not os.path.exists(self.path):
os.mkdir(self.path)
def process_item(self, item, spider):
category = item['category']
urls = item['urls']
category_path = os.path.join(self.path, category)
if not os.path.exists(category_path):
os.mkdir(category_path)
for url in urls:
# 通过__来分割得到文件名
image_name = url.split('__')[-1]
request.urlretrieve(url, os.path.join(category_path, image_name))
return item
运行后的结果是:
这个方法可以实现我们想要的效果。但是,还不够优雅,没有用到多线程,不够简洁。 scrapy为下载item包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的item pipelines。这些pipeline有共同的方法和结构(我们称之为media pipelines)。一般来说你会使用Files Pipeline或者Images Pipeline
使用scrapy内置的下载文件的方法有如下好处:
- 避免重新下载最近已经下载过的数据
- 可以方便的指定文件存储的路径。
- 可以将下载的图片转换成通用的格式,比如png或jpg。
- 可以方便的生成缩略图。
- 可以方便的检测图片的宽和高,确保他们满足最小限制。
- 异步下载,效率非常高。
Files Pipeline的使用步骤:
- 定义好一个Item,然后在这个item中定义两个属性,分别为file_url以及files。file_urls 是用来存储需要下载的文件的url链接的,需要的是一个列表。
- 当文件下载完成后,会把文件下载的相关信息存储到items中的files属性中,比如下载路径,下载的url和文件的校验码等。
- 在配置文件settings.py中配置
FILES_STORE
,这个配置是用来设置文件下载下来的路径。 - 启动pipeline:在ITEM_PIPLINES中设置
'scrapy.pipelines.files.FilePipeline':1
。
Images Pipeline的使用步骤:
当使用Image Pipeline下载文件的时候,按照以下步骤来完成:
- 定义好一个
Item
,然后在这个item
中定义两个属性,分别为image_urls
以及images
。image_url
是用来存储需要下载的图片的url链接,需要给一个列表。 - 当图片下载完成之后,会把图片下载的相关信息存储到
item
的imags属性中,比如下载路径,下载的url和图片的校验码等。 - 当配置文件
settings.py
中配置IMAGES_STORE
,这个配置是用来设置图片下载下来的路径的。 - 启动pipeline:在ITEM_PIPELINES中设置
'scrapy.pipelines.images.ImagesPipeline':1
。
下面我们就使用Image Pipeline来实现下这个功能,详细的步骤如下:
1. 修改bbaImgDemoItem类
代码语言:javascript复制class bbaImgDemoItem(scrapy.Item):
category = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
在bbaImgDemoItem类中定义image_urls和images两个属性。
2. 修改bba3Spider类,将下载的图片路径放到image_urls中。
代码语言:javascript复制class bba3Spider(scrapy.Spider):
name = 'bba3'
allowed_domains = ['car.autohome.com.cn']
start_urls = ['https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66.html#pvareaid=2042214']
def parse(self, response):
# 获取所有类别
uiboxs = response.xpath("//div[@class='uibox']")[1:]
for uibox in uiboxs:
# 获取某个类别的名称
category = uibox.xpath(".//div[@class='uibox-title']/a/text()").get()
# 获取图片链接
org_urls = uibox.xpath(".//ul/li/a/img/@src").getall()
urls = list(map(lambda url: response.urljoin(url), org_urls))
imgDemoItem = bbaImgDemoItem(category=category, image_urls=urls)
yield imgDemoItem
3. 修改settings.py
在settings.py文件配置IMAGES_STORE。指定图片的保存路径。
代码语言:javascript复制import os
IMAGES_STORE= os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')
4. 指定启动的pipeline
将启动的pipeline指定为scrapy.pipelines.images.ImagesPipeline
ITEM_PIPELINES = {
# 'bba_img_demo.pipelines.bbaImgDemoPipeline': 300,
'scrapy.pipelines.images.ImagesPipeline':1
}
这样设置之后,原先的bbaImgDemoPipeline则不会被启动。因为只有一个pipeline,所以优先级可以随意设置。 经过这四步设置基本上差不多了。但是,如果此时你直接运行的话,大概率得不到想要的效果。这是因为ImagesPipeline类在初始化时需要引入PIL包。如果没有安装的话则会直接报错。
5. 安装Pillow库
代码语言:javascript复制pip install Pillow
经过上面五步之后就可以正确的运行了。运行之后的结果是:
可以看出所有的图片都被保存到了images目录下的full文件夹下了。这显然也不是我们期望的结果。所以我们还是需要继承 ImagesPipeline类,然后,重写其保存逻辑。查看ImagesPipeline源代码可以得知将图片保存到full文件夹的方法是file_path。所以我们只需要重写这个方法返回我们想要的路径即可。
指定我们需要保存的路径
在pipelines.py 文件中自定义一个名为bbaImagesPipeline的类,让该类继承自ImagesPipeline。
- 重写get_media_requests方法 get_media_requests方法在发送下载请求之前调用,该方法主要的作用是拿到image_urls中的图片链接,并拼接成下载请求。 ImagesPipeline类的get_media_requests方法,可以看出返回的是一个Request对象的列表。
def get_media_requests(self, item, info):
urls = ItemAdapter(item).get(self.images_urls_field, [])
return [Request(u) for u in urls]
重写后的get_media_requests方法。首先调用父类的image_urls方法,然后将item设置到request_obj中。
代码语言:javascript复制 def get_media_requests(self, item, info):
request_objs = super(bbaImagesPipeline, self).get_media_requests(item, info)
for request_obj in request_objs:
request_obj.item = item
return request_objs
2. 重写file_path方法
父类的file_path方法主要就两步,第一步是将图片链接的地址做hash运算得到图片的名称,接着返回图片存储的相对路径 full/{image_guid}.jpg
。
def file_path(self, request, response=None, info=None, *, item=None):
image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
return f'full/{image_guid}.jpg'
所以重写file_path方法只需要将返回的相对路径替换成我们期望的相对路径。
代码语言:javascript复制 def file_path(self, request, response=None, info=None, *, item=None):
path = super(bbaImagesPipeline, self).file_path(request, response, info)
category = request.item.get('category')
img_name = path.replace('full/', '')
image_path = os.path.join(category, img_name)
return image_path
调用父类的file_path方法得到返回的full/{image_guid}.jpg
。接着获取item中的category属性。
然后就是将full/
替换掉就得到了图片名称。最后就是将分类和图片名称拼接成一个相对路径返回。
爬取高清图片(多个网页同时爬取)
1. 分析链接特点
- 车身外观的地址: https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66-1-p2.html
- 中控方向盘的地址:https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66-10.html#pvareaid=2042223
简单分析下可以得出链接中 https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66 这部分是完全一样的,后面的部分的可以匹配任意字符。
所以匹配链接地址的正则表达式是
https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66.
。
2. 编写爬虫代码
这里自定义了一个名为bba3Spider类,该类继承自CrawlSpider。
代码语言:javascript复制 # 定义好爬取策略
rules = (
Rule(LinkExtractor(allow=r"https://car.autohome.com.cnhttps://img.yuanmabao.com/zijie/pic/series/66. "), callback="parse_page", follow=True),
)
callback指定调用的回调函数是parse_page方法。当点击下一页时则继续往下走。 回调方法parse_page,该方法爬取分类和图片的地址。
代码语言:javascript复制 def parse_page(self, response):
category = response.xpath("//div[@class='uibox']/div/text()").get()
srcs = response.xpath('//div[contains(@class,"uibox-con")]/ul/li/a/img/@src').getall()
urls = list(map(lambda x: response.urljoin(x), srcs))
yield bbaImgDemoItem(category=category, image_urls=urls)
总结
本文通过以某网站为例说明了如何利用scrapy框架来高效的爬取网站中的图片。