Python使用Scrapy下載圖片的兩種方式
在本篇博客中,我們將探討如何使用 Scrapy 框架下載圖片,并詳細解釋兩種下載圖片的方式。
一、項目結(jié)構(gòu)
首先,我們假設項目結(jié)構(gòu)如下:
biantu_down_pic/ ├── biantu_down_pic/ │ ├── __init__.py │ ├── items.py │ ├── middlewares.py │ ├── pipelines.py │ ├── settings.py │ └── spiders/ │ ├── __init__.py │ └── down_pic.py ├── log_file.log └── scrapy.cfg
二、settings.py 配置
在 settings.py 中,我們需要進行一些基本配置:
# Scrapy settings for biantu_down_pic project
BOT_NAME = 'biantu_down_pic'
SPIDER_MODULES = ['biantu_down_pic.spiders']
NEWSPIDER_MODULE = 'biantu_down_pic.spiders'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'WARNING'
LOG_FILE = './log_file.log'
USER_AGENTS_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.111 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
# 更多 User Agent 可添加
]
DOWNLOADER_MIDDLEWARES = {
'biantu_down_pic.middlewares.BiantuDownPicDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
'biantu_down_pic.pipelines.BiantuDownPicSavePipeline': 300, # 先下載圖片再保存到數(shù)據(jù)庫
'biantu_down_pic.pipelines.BiantuDownPicPipeline': 301,
'biantu_down_pic.pipelines.MysqlPipeline': 302,
}
# 圖片地址配置
IMAGES_STORE = 'D:/ruoyi/pic.baidu.com/uploadPath'
# MySQL 配置
MYSQL_HOST = "localhost"
MYSQL_USER = "drawing-bed"
MYSQL_PASSWORD = "HBt5J7itWJEXe"
MYSQL_DBNAME = "drawing-bed"
MYSQL_PORT = 3306
三、爬蟲文件(down_pic.py)
在 down_pic.py 文件中,我們定義了爬蟲類 DownPicSpider,用于從網(wǎng)頁 https://pic.baidu.com/ 下載圖片。
代碼示例
import imghdr
import json
import os
import re
from datetime import datetime
import scrapy
from biantu_down_pic.items import BiantuDownPicItem
from biantu_down_pic.settings import IMAGES_STORE
from biantu_down_pic.pipelines import sanitize_filename
class DownPicSpider(scrapy.Spider):
name = 'down_pic'
start_urls = ['https://pic.baidu.com/']
def __init__(self, token=None, map_json=None, *args, **kwargs):
super(DownPicSpider, self).__init__(*args, **kwargs)
self.token = token
self.map_json = json.loads(map_json) if map_json else {}
def parse(self, response, **kwargs):
# 從 map_json 獲取必要的信息
key_id = self.map_json.get('id')
url = self.map_json.get('url')
title = self.map_json.get("title")
# 設置 cookies
cookies = {i.split('=')[0]: i.split('=')[1] for i in self.token.split('; ')}
# 發(fā)起請求到詳情頁
yield scrapy.Request(
url,
cookies=cookies,
callback=self.parse_detail,
meta={'key_id': key_id, 'url': url, 'cookies': cookies, 'title': title}
)
def parse_detail(self, response, **kwargs):
# 從 URL 中提取圖片 ID
pic_id = response.url.split('/')[-1].split('.')[0]
cookies = response.meta['cookies']
# 構(gòu)建請求源圖片 URL 的地址
source_url = f'https://pic.baidu.com/e/extend/downpic.php?id={pic_id}'
# 獲取縮略圖 URL
thumbnail_url = response.xpath('//*[@id="img"]/img/@src').extract_first()
thumbnail_url = response.urljoin(thumbnail_url)
# 將縮略圖 URL 添加到 meta 中
response.meta['thumbnail_url'] = thumbnail_url
# 發(fā)送請求獲取源圖片 URL
yield scrapy.Request(
source_url,
cookies=cookies,
callback=self.parse_source_url,
meta=response.meta
)
def parse_source_url(self, response, **kwargs):
# 解析響應中的圖片下載鏈接
pic_data = response.json()
pic_url = response.urljoin(pic_data['pic'])
cookies = response.meta['cookies']
# 發(fā)送請求下載圖片
yield scrapy.Request(
pic_url,
cookies=cookies,
callback=self.save_image,
meta=response.meta
)
def save_image(self, response, **kwargs):
# 動態(tài)生成文件保存路徑
current_time = datetime.now()
date_path = current_time.strftime('%Y/%m/%d')
download_dir = os.path.join(IMAGES_STORE, 'pic', date_path).replace('\\', '/')
# 生成清理后的文件名
title = response.meta['title']
pic_name = sanitize_filename(title)
pic_name = self.clean_filename(pic_name)
# 根據(jù)響應頭獲取文件擴展名
content_type = response.headers.get('Content-Type', b'').decode('utf-8')
extension = self.get_extension(content_type, response.body)
if not pic_name.lower().endswith(extension):
pic_name += extension
file_path = os.path.join(download_dir, pic_name).replace('\\', '/')
# 確保下載目錄存在
os.makedirs(download_dir, exist_ok=True)
# 保存圖片
with open(file_path, 'wb') as f:
f.write(response.body)
# 構(gòu)建 Item 并返回
item = BiantuDownPicItem()
item['id'] = response.meta['key_id']
item['title'] = response.meta['title']
item['title_min'] = response.meta['title'] + '_min.jpg'
item['url'] = response.meta['url']
item['min_url'] = response.meta['thumbnail_url']
item['download_path'] = download_dir.replace(IMAGES_STORE, '')
item['max_path'] = file_path.replace(IMAGES_STORE, '/profile')
yield item
@staticmethod
def clean_filename(filename):
"""清理文件名,去除無效字符"""
return re.sub(r'[<>:"/\\|?*]', '', filename)
@staticmethod
def get_extension(content_type, body):
"""根據(jù)內(nèi)容類型或文件內(nèi)容獲取擴展名"""
if 'image/jpeg' in content_type:
return '.jpg'
elif 'image/png' in content_type:
return '.png'
elif 'image/jpg' in content_type:
return '.jpg'
else:
return '.' + imghdr.what(None, body)
四、管道文件(pipelines.py)
在 pipelines.py 文件中,我們定義了兩個管道類,用于處理和保存下載的圖片。
代碼示例
import logging
import re
import scrapy
from scrapy.pipelines.images import ImagesPipeline
log = logging.getLogger(__name__)
class BiantuDownPicPipeline:
"""基礎的 Item 處理管道,僅打印 Item"""
def process_item(self, item, spider):
print(item)
return item
def sanitize_filename(filename):
"""移除文件名中的非法字符,替換為 'X'"""
return re.sub(r'[<>:"/\\|?*]', 'X', filename)
class BiantuDownPicSavePipeline(ImagesPipeline):
"""自定義圖片下載和保存管道"""
def get_media_requests(self, item, info):
"""發(fā)送請求去下載縮略圖"""
min_url = item['min_url']
print('1. 發(fā)送請求去下載圖片, min_url:', min_url)
return scrapy.Request(url=min_url)
def file_path(self, request, response=None, info=None, *, item=None):
"""定義圖片的存儲路徑"""
print('2. 圖片的存儲路徑')
filename = sanitize_filename(item['title_min'])
download_path = f"{item['download_path']}/{filename}"
return download_path
def item_completed(self, results, item, info):
"""更新 Item,添加縮略圖的存儲路徑"""
print(f'3. 對 Item 進行更新, result: {results}')
if results:
ok, res = results[0]
if ok:
item['min_path'] = '/profile' + res["path"]
return item
class MysqlPipeline(object):
"""MySQL 數(shù)據(jù)庫管道,暫時保留現(xiàn)狀"""
def __init__(self, host, user, password, database, port):
self.host = host
self.user = user
self.password = password
self.database = database
self.port = port
@classmethod
def from_crawler(cls, crawler):
return cls(
host=crawler.settings.get("MYSQL_HOST"),
user=crawler.settings['MYSQL_USER'],
password=crawler.settings['MYSQL_PASSWORD'],
database=crawler.settings['MYSQL_DBNAME'],
port=crawler.settings['MYSQL_PORT']
)
def open_spider(self, spider):
"""在爬蟲啟動時創(chuàng)建數(shù)據(jù)庫連接"""
self.conn = pymysql.connect(
host=self.host,
user=self.user,
password=self.password,
database=self.database,
charset='utf8',
port=self.port
)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
"""處理 Item 并插入數(shù)據(jù)庫"""
dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# SQL 語句暫時保留,待更新
# insert_sql = """
# INSERT INTO biz_pic (
# url, title, source_url, category_name, size, volume, create_by, create_time
# ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
# """
# try:
# self.cursor.execute(insert_sql, (
# item['url'],
# item['title'],
# item['source_url'],
# item['category_name'],
# item['size'],
# item['volume'],
# 'admin',
# dt
# ))
# self.conn.commit()
# except Exception as e:
# log.error(f"插入數(shù)據(jù)出錯,{e}")
def close_spider(self, spider):
"""在爬蟲關閉時關閉數(shù)據(jù)庫連接"""
self.cursor.close()
self.conn.close()
log.warning("爬取數(shù)據(jù)結(jié)束===============================>end")
五、兩種下載方式的區(qū)別和使用場景
在這篇文章中,我們介紹了使用 Scrapy 下載圖片的兩種不同方式:直接鏈接下載和通過下載鏈接下載。接下來,我們將詳細介紹這兩種方式的區(qū)別以及它們適用的場景。
1. 直接鏈接下載
直接鏈接下載是指圖片的 URL 本身就指向圖片文件,可以直接通過 URL 獲取圖片內(nèi)容。
示例代碼
在 pipelines.py 中的 BiantuDownPicSavePipeline 類中,我們使用直接鏈接下載圖片:
class BiantuDownPicSavePipeline(ImagesPipeline):
"""自定義圖片下載和保存管道"""
def get_media_requests(self, item, info):
"""發(fā)送請求去下載縮略圖"""
min_url = item['min_url']
print('1. 發(fā)送請求去下載圖片, min_url:', min_url)
return scrapy.Request(url=min_url)
def file_path(self, request, response=None, info=None, *, item=None):
"""定義圖片的存儲路徑"""
print('2. 圖片的存儲路徑')
filename = sanitize_filename(item['title_min'])
download_path = f"{item['download_path']}/{filename}"
return download_path
def item_completed(self, results, item, info):
"""更新 Item,添加縮略圖的存儲路徑"""
print(f'3. 對 Item 進行更新, result: {results}')
if results:
ok, res = results[0]
if ok:
item['min_path'] = '/profile' + res["path"]
return item
使用場景
- 簡單且常見:適用于絕大多數(shù)公開訪問的圖片文件。
- 性能較好:直接通過 URL 獲取圖片,無需額外的請求和處理。
2. 通過下載鏈接下載
通過下載鏈接下載是指圖片的 URL 需要通過一個中間鏈接來獲取實際的圖片文件。這種情況通常用于需要驗證或有時效性的下載鏈接。
示例代碼
在 down_pic.py 中的 DownPicSpider 類中,我們使用通過下載鏈接下載圖片:
class DownPicSpider(scrapy.Spider):
name = 'down_pic'
start_urls = ['https://pic.baidu.com/']
def parse_detail(self, response, **kwargs):
# 從 URL 中提取圖片 ID
pic_id = response.url.split('/')[-1].split('.')[0]
cookies = response.meta['cookies']
# 構(gòu)建請求源圖片 URL 的地址
source_url = f'https://pic.baidu.com/e/extend/downpic.php?id={pic_id}'
# 獲取縮略圖 URL
thumbnail_url = response.xpath('//*[@id="img"]/img/@src').extract_first()
thumbnail_url = response.urljoin(thumbnail_url)
# 將縮略圖 URL 添加到 meta 中
response.meta['thumbnail_url'] = thumbnail_url
# 發(fā)送請求獲取源圖片 URL
yield scrapy.Request(
source_url,
cookies=cookies,
callback=self.parse_source_url,
meta=response.meta
)
def parse_source_url(self, response, **kwargs):
# 解析響應中的圖片下載鏈接
pic_data = response.json()
pic_url = response.urljoin(pic_data['pic'])
cookies = response.meta['cookies']
# 發(fā)送請求下載圖片
yield scrapy.Request(
pic_url,
cookies=cookies,
callback=self.save_image,
meta=response.meta
)
使用場景
- 需要身份驗證或時效性的下載鏈接:適用于需要通過特定請求獲取下載鏈接的情況。
- 安全性要求較高:適用于下載需要權(quán)限控制的圖片。
總結(jié)
- 直接鏈接下載:適用于絕大多數(shù)公開訪問的圖片文件,操作簡單且性能較好。
- 通過下載鏈接下載:適用于需要通過驗證或時效性鏈接獲取圖片的情況,安全性更高但操作稍復雜。
到此這篇關于Python使用Scrapy下載圖片的兩種方式的文章就介紹到這了,更多相關Python Scrapy下載圖片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
urllib和BeautifulSoup爬取維基百科的詞條簡單實例
這篇文章主要介紹了urllib和BeautifulSoup爬取維基百科的詞條簡單實例,具有一定借鑒價值,需要的朋友可以參考下2018-01-01
Python使用vllm處理多模態(tài)數(shù)據(jù)的預處理技巧
本文深入探討了在Python環(huán)境下使用vLLM處理多模態(tài)數(shù)據(jù)的預處理技巧,我們將從基礎概念出發(fā),詳細講解文本、圖像、音頻等多模態(tài)數(shù)據(jù)的預處理方法,重點介紹如何利用vLLM框架高效處理這些數(shù)據(jù),需要的朋友可以參考下2025-07-07
Python如何用str.format()批量生成網(wǎng)址(豆瓣讀書為例)
這篇文章主要介紹了Python如何用str.format()批量生成網(wǎng)址(豆瓣讀書為例),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09

