مقدمه
در این راهنمای عملی برای توسعهدهندگان پایتون سطح متوسط بهصورت گامبهگام روشهای رایج و پیشرفتهٔ دانلود تصاویر از وب (وب اسکریپینگ/اسکریپ کردن تصاویر) پوشش داده شده است. پس از خواندن این مقاله شما میتوانید تصاویر تکتک یا دستهای را دانلود، پردازش، و با ملاحظات مربوط به خطا، کارایی و اخلاقی/قانونی اجرا کنید.
مباحثی که بررسی میکنیم شامل: پایههای دانلود باینری، روشهای همگام و ناهمگام (requests, urllib, aiohttp)، ترکیب با پردازش تصویر (Pillow), استخراج URLها با BeautifulSoup و فریمورکهای قدرتمند مثل Scrapy و اتوماسیون با Selenium است. همچنین نکات خطاها، بهینهسازی و بهترینشیوهها را خواهید دید.
مفاهیم پایه
چند نکتهٔ مهم قبل از شروع:
- MIME type (مثلاً image/jpeg, image/png) در هدر HTTP تعیین میکند با چه فرمتی مواجه هستید.
- تصاویر فایلهای باینریاند؛ همیشه هنگام نوشتن روی دیسک از حالت باینری ('wb') استفاده کنید.
- بررسی status code (مثلاً 200) و استفاده از مدیریت استثناها برای پایداری الزامی است.
روش ساده: requests
برای سناریوهای ساده و همگام، requests معمولاً بهترین شروع است.
نمونهٔ دانلود یک تصویر:
import requests
url = 'https://cdn.pixabay.com/photo/2023/11/16/05/02/mountains-8391433_640.jpg'
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
with open('requests.jpg', 'wb') as f:
f.write(resp.content)
توضیح:
- ورودی: url؛ خروجی: فایل باینری روی دیسک.
- requests.get پاسخ را برمیگرداند؛ با resp.content بایتها را میگیریم.
- خطبهخط: ارسال GET → بررسی کد → باز کردن فایل در 'wb' → نوشتن بایتها.
دانلود چند تصویر (با نامگذاری یکتا):
import requests
image_urls = ['url1', 'url2', 'url3']
for i, url in enumerate(image_urls, 1):
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
with open(f'image_{i}.jpg', 'wb') as f:
f.write(resp.content)
نکات عملی:
- برای تعداد زیاد از requests.Session() استفاده کنید تا اتصالهای TCP باز مجدد شوند.
- همیشه timeout تعیین کنید تا عملیات بلاک نماند.
- در مواجهه با خطا از مکانیزم retry (مثلاً با paket tenacity یا یک حلقهٔ ساده) استفاده کنید.
Requests + Pillow برای پردازش
گاهی میخواهید تصویر را بلافاصله پردازش کنید (تغییر اندازه، استخراج EXIF و...). ترکیب requests و Pillow مناسب است.
import requests
from PIL import Image
from io import BytesIO
resp = requests.get(url, timeout=10)
if resp.status_code == 200:
img = Image.open(BytesIO(resp.content))
img = img.resize((800, 600))
img.save('resized.jpg')
توضیح:
- ورودی: resp.content (بایت) → تبدیل به فایل-مانند با BytesIO → باز شدن توسط Image.open.
- خروجی: فایل قابلخواندن با متادیتا (در صورت نیاز EXIF استخراج میشود).
استخراج URL و دانلود دستهای با BeautifulSoup
برای جمعآوری URLها از صفحات HTML از BeautifulSoup استفاده کنید.
import requests
from bs4 import BeautifulSoup
page = requests.get('https://unsplash.com', timeout=10)
soup = BeautifulSoup(page.content, 'html.parser')
img_tags = soup.find_all('img')
urls = [img.get('src') for img in img_tags if img.get('src')]
# سپس همین لیست را با requests دانلود کنید
توضیح مرحلهبهمرحله:
- GET صفحه → ایجاد BeautifulSoup → یافتن تگهای <img>.
- فیلتر کردن URLها (مقادیر src یا data-src) و نرمالایز کردن آدرسهای نسبی با urllib.parse.urljoin.
راهحلِ داخلی: urllib
اگر نمیخواهید وابستگی خارجی اضافه کنید، از urllib.request استفاده کنید و هدر User-Agent بگذارید تا برخی سرورها بلاک نکنند.
import urllib.request
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=10) as resp, open('urllib.jpg', 'wb') as f:
f.write(resp.read())
نکات: urllib برای اسکریپهای سریع و بدون وابستگی مفید است اما سطح بالاتر امکاناتی مانند session pooling یا retry ندارد.
دانلود ناهمگام با aiohttp
برای دانلود همزمان تعداد زیادی تصویر، aiohttp میتواند سرعت را بهشکل چشمگیری افزایش دهد.
import aiohttp
import asyncio
async def download(url, filename, session):
async with session.get(url, timeout=10) as resp:
if resp.status == 200:
data = await resp.read()
with open(filename, 'wb') as f:
f.write(data)
async def main(urls):
async with aiohttp.ClientSession() as session:
tasks = [download(u, f'aio_{i}.jpg', session) for i,u in enumerate(urls,1)]
await asyncio.gather(*tasks)
# asyncio.run(main(urls))
توضیح:
- هر download یک coroutine است که با session مشترک اجرا میشود.
- از asyncio.gather برای اجرای همزمان استفاده کنید؛ مراقب محدود کردن همزمانی (semaphore) برای جلوگیری از فشار روی سرور باشید.
سطح پایین: http.client
برای کنترل دقیقتر اتصال HTTP میتوانید از http.client استفاده کنید؛ اما کد verboseتر است و معمولاً در عمل نیاز ندارید مگر برای حالات خاص.
import http.client
import urllib.parse
parsed = urllib.parse.urlparse(url)
conn = http.client.HTTPSConnection(parsed.netloc)
conn.request('GET', parsed.path)
resp = conn.getresponse()
if resp.status == 200:
with open('httpclient.jpg', 'wb') as f:
f.write(resp.read())
conn.close()
خلاصه: سطح پایینتر اما کنترل headers و اتصال را دقیقتر در اختیار میدهد.
اسکریپینگ ساختاریافته با Scrapy
برای پروژههای بزرگ و خزیدن مداوم از Scrapy استفاده کنید؛ مدیریت concurrency، middleware و pipeline برای ذخیره/پردازش تصویر دارد.
import scrapy
from scrapy.crawler import CrawlerProcess
class ImageSpider(scrapy.Spider):
name = 'image_spider'
start_urls = ['https://unsplash.com']
def parse(self, response):
imgs = response.css('img::attr(src)').getall()
for i, src in enumerate(imgs[:10],1):
yield scrapy.Request(src, callback=self.save_image, meta={'filename': f'scrapy_{i}.jpg'})
def save_image(self, response):
fn = response.meta['filename']
with open(fn, 'wb') as f:
f.write(response.body)
# process = CrawlerProcess(); process.crawl(ImageSpider); process.start()
نکته: Scrapy امکاناتی مثل ImagesPipeline دارد که دانلود، کش و پردازش را اتوماتیک میکند—برای دیتاستها مناسب است.
اتوماسیون مرورگر با Selenium
اگر تصاویر توسط جاوااسکریپت یا لِیزیلود تولید میشوند، از Selenium برای رندر کردن صفحه استفاده کنید. سپس URL تصویر را گرفته و با requests/download کنید.
from selenium import webdriver
from selenium.webdriver.common.by import By
import requests
driver = webdriver.Chrome()
driver.get('https://unsplash.com')
img = driver.find_element(By.TAG_NAME, 'img')
src = img.get_attribute('src')
resp = requests.get(src)
if resp.status_code == 200:
with open('selenium.jpg','wb') as f:
f.write(resp.content)
driver.quit()
هشدار: Selenium منابع زیادی مصرف میکند و برای حجم بالای دانلود مناسب نیست مگر در ترکیب با headless و بهینهسازی.
مفاهیم پیشرفته
چند تکنیک که در پروژههای واقعی مفید است:
پیشرفت دانلود و استریم
برای فایلهای بزرگ از stream=True در requests و نوشتن chunk به chunk استفاده کنید تا حافظه معقول بماند و امکان نمایش نوار پیشرفت وجود داشته باشد.
import requests
from tqdm import tqdm
resp = requests.get(url, stream=True)
total = int(resp.headers.get('content-length', 0))
with open('big.jpg','wb') as f:
for chunk in tqdm(resp.iter_content(1024), total=total//1024, unit='KB'):
if chunk:
f.write(chunk)
حفظ متادیتا (EXIF)
from PIL import Image
from io import BytesIO
# پس از دانلود
img = Image.open(BytesIO(resp.content))
exif = img.getexif()
img.save('with_exif.jpg', exif=exif)
مطالعهٔ موردی: Unsplash — جمعآوری چند تصویر
برای انتخاب بین روشها توجه کنید:
- تعداد کم و ساده: requests
- پردازش بلافاصله: requests + Pillow
- تعداد زیاد و همزمان: aiohttp یا Scrapy با pipeline
- محتوای دینامیک: Selenium
پیشنهاد عملی: ابتدا با BeautifulSoup یا Scrapy URLها را جمعآوری کنید، سپس با aiohttp یا requests+Session دانلود دستهای را انجام دهید، و در نهایت با Pillow پردازش کنید.
مشکلات رایج و رفع آنها
- قطع شبکه/timeout: از retry و افزایش timeout استفاده کنید.
- 403/401: بررسی headers، کوکیها، احراز هویت و رعایت قوانین سایت.
- فایلهای ناقص/خراب: از content-length و checksum برای اعتبارسنجی استفاده کنید.
- محدودیت نرخ (rate limiting): بین درخواستها تاخیر بگذارید یا از پروکسی/تور استفاده کنید و robots.txt را بررسی کنید.
- اشکال نوشتن فایل: مجوزهای دایرکتوری را بررسی کنید و مسیرهای مطمئن بسازید.
بهترینشیوهها
- همیشه قوانین سایت و robots.txt را چک کنید و حقوق مالکیت معنوی را رعایت کنید.
- از هدرهای معقول (User-Agent) و session pooling استفاده کنید.
- محدودیت همزمانی را رعایت و با Semaphore یا محدودکنندهٔ درخواست، فشار را کنترل کنید.
- لگگیری (logging) و metric برای مانیتورینگ خطاها و رفتار اسکریپر فعال کنید.
- در پروژههای بزرگ از ابزارهای آماده (Scrapy pipeline، job scheduler) استفاده کنید تا مقیاسپذیر بماند.
جمعبندی
برای دانلود تصاویر با Python یک طیف ابزار دارید: از requests و urllib برای کارهای ساده تا aiohttp و Scrapy برای مقیاسپذیری و از Selenium برای صفحات دینامیک. انتخاب صحیح وابسته به حجم، پیچیدگی صفحه، نیاز به پردازش و محدودیتهای قانونی/عملی است. با رعایت مدیریت خطا، محدودیت نرخ و حفظ متادیتا میتوانید یک فرایند پایدار و اخلاقی برای اسکریپینگ تصاویر بسازید.





