مقدمه
اگر با Scrapy دادهها را از وب جمعآوری میکنید، نیاز دارید آنها را بهصورت پایدار ذخیره کنید. PostgreSQL انتخابی مطمئن و مقیاسپذیر برای ذخیرهٔ دادههای ساختاریافته است. در این راهنما گامبهگام میبینیم چگونه با استفاده از Item Pipelines در Scrapy دادهها را وارد یک دیتابیس Postgres کنیم، از ذخیرهٔ دادههای تکراری جلوگیری کنیم و نکات عملی برای امنیت و عملکرد را بررسی میکنیم. در پایان این مطلب شما قادر خواهید بود یک pipeline عملی برای ذخیره در PostgreSQL بنویسید، آن را در تنظیمات Scrapy فعال کنید و روشهای بهینهسازی را پیادهسازی کنید.
آشنایی با Item Pipelines در Scrapy
Item Pipeline مکانیسمی است که آیتمهای استخراجشده توسط اسپایدرها را پردازش میکند. هر آیتم پس از استخراج به ترتیب از چند pipeline عبور میکند. چند وظیفهٔ رایج pipelines عبارتند از:
- پاکسازی و نرمالسازی فیلدها
- اعتبارسنجی مقادیر
- حذف یا گزارش آیتمهای تکراری
- ذخیرهسازی نهایی (فایل، دیتابیس، سرویس خارجی)
توابع مهم که معمولاً در یک pipeline تعریف میشوند:
- __init__: مقداردهی اولیه، مانند بازکردن اتصال به دیتابیس
- process_item(self, item, spider): هر آیتم را دریافت و پردازش میکند؛ معمولاً در اینجا INSERT یا UPDATE انجام میشود
- close_spider(self, spider): هنگام پایان اجرا برای بستن منابع استفاده میشود
راهاندازی PostgreSQL
میتوانید Postgres را محلی نصب کنید یا از نسخهٔ هاستشده استفاده کنید. اطلاعات اتصال معمولاً شامل میزبان، نام دیتابیس، نام کاربری و رمز است. نکتهٔ امنیتی: هرگز رمز را هاردکد نکنید؛ از متغیرهای محیطی یا سرویسهای مدیریت اسرار استفاده کنید.
# نمونه پارامترهای اتصال (به صورت نمایشی، از محیط واقعی جدا کنید)
نصب وابستگیها
برای کار با PostgreSQL از Python معمولاً از psycopg2 استفاده میشود. بستهٔ قابل نصب و سادهتر برای توسعه psycopg2-binary است (برای تولیدی بهتر است از بستهٔ مناسب سیستم استفاده کنید).
pip install psycopg2-binary
نوشتن Pipeline پایه برای ذخیرهسازی
در فایل pipelines.py یک pipeline ساده مینویسیم که هنگام مقداردهی اولیه به دیتابیس متصل شده، جدول را ایجاد میکند و در process_item داده را درج میکند. در کد زیر از متغیرهای محیطی برای جزئیات اتصال استفاده شده است تا از هاردکد جلوگیری شود.
# pipelines.py
import os
import psycopg2
from psycopg2 import sql
class PostgresDemoPipeline:
def __init__(self):
# خواندن پارامترها از محیط برای امنیت
hostname = os.getenv('PGHOST', 'localhost')
username = os.getenv('PGUSER', 'postgres')
password = os.getenv('PGPASSWORD', '')
database = os.getenv('PGDATABASE', 'quotes')
# اتصال به دیتابیس
self.connection = psycopg2.connect(host=hostname, user=username, password=password, dbname=database)
self.cur = self.connection.cursor()
# ایجاد جدول (اگر موجود نباشد)
self.cur.execute("""
CREATE TABLE IF NOT EXISTS quotes(
id serial PRIMARY KEY,
content text,
tags text,
author varchar(255)
)
""")
self.connection.commit()
def process_item(self, item, spider):
# درج آیتم با پارامترایز کردن مقادیر برای جلوگیری از SQL injection
insert_query = "INSERT INTO quotes (content, tags, author) VALUES (%s, %s, %s)"
self.cur.execute(insert_query, (
item.get('text'),
str(item.get('tags', [])),
item.get('author')
))
self.connection.commit()
return item
def close_spider(self, spider):
# بستن منابع
self.cur.close()
self.connection.close()توضیح اجزا:
- ورودیها: آیتمهای Scrapy که معمولاً دیکشنریمانند هستند و فیلدهایی مثل text, tags, author دارند.
- خروجی: آیتم بازگردانیشده (Scrapy برای زنجیرهٔ pipeline به آن نیاز دارد).
- نقش هر بخش: __init__ اتصال و آمادهسازی جدول، process_item درج داده و commit، close_spider بستن اتصال.
جلوگیری از ذخیرهٔ دادههای تکراری
دو رویکرد معمول برای جلوگیری از تکرار وجود دارد:
- پرسوجو قبل از درج (SELECT → INSERT) — ساده اما مستعد رقابت (race condition)
- استفاده از قیدهای دیتابیس و عملیات atomic مانند INSERT ... ON CONFLICT DO NOTHING — امنتر و سریعتر
نمونهٔ امن با استفاده از قید یکتا و upsert:
# در __init__ پس از ایجاد جدول، یک ایندکس یکتا ایجاد کنید
self.cur.execute("CREATE UNIQUE INDEX IF NOT EXISTS uq_quotes_content ON quotes (md5(content));")
self.connection.commit()
# سپس در process_item از ON CONFLICT استفاده کنید
upsert_query = (
"INSERT INTO quotes (content, tags, author) VALUES (%s, %s, %s) "
"ON CONFLICT DO NOTHING"
)
self.cur.execute(upsert_query, (item.get('text'), str(item.get('tags', [])), item.get('author')))
self.connection.commit()توضیح: در این مثال برای سادگی از md5(content) در ایندکس نام بردیم؛ بهتر است یک ستون hash مجزا یا constraint یکتا مناسب (مثلاً روی content یا شناسهٔ محاسبهشده) ایجاد کنید. روش ON CONFLICT درج را اتمیک میکند و از مشکلات همزمانی جلوگیری مینماید.
رفع خطا، بازگشت تراکنش و پایداری
چند نکتهٔ عملی برای پایدار نگه داشتن pipeline:
- همیشه اجرای کوئریها را در بلوک try/except قرار دهید و در صورت خطا connection.rollback() کنید تا تراکنش در وضعیت نامشخص نماند.
- برای بارگذاری انبوه از روشهای batch یا COPY استفاده کنید تا سرعت خیلی بهتر شود.
- در پروژههای پر ترافیک از pooling مانند psycopg2.pool یا سرویسهای connection pooler استفاده کنید تا افت عملکرد ناشی از باز و بسته کردن مکرر اتصال کاهش یابد.
- برای بازیابی خودکار از خطاها استراتژی retry با backoff پیاده کنید.
تنظیمات Scrapy برای فعالسازی Pipeline
برای فعال کردن pipeline آن را در settings.py ثبت کنید. عدد اولویت نشان میدهد ترتیب اجرا چگونه است (عدد کوچکتر اول اجرا میشود):
ITEM_PIPELINES = {
'postgres_demo.pipelines.PostgresDemoPipeline': 300,
# یا برای جلوگیری از تکرار:
# 'postgres_demo.pipelines.PostgresNoDuplicatesPipeline': 300,
}
نکات طراحی اسکیمای دیتابیس
چند توصیه برای طراحی جدول:
- برای فیلدهای تگ اگر میخواهید کوئریهای جستجو انجام دهید، از آرایهٔ Postgres یا جدول رابطهای مجزا استفاده کنید؛ ذخیرهٔ رشتهٔ پراکنده آسان ولی محدود است.
- ایندکسگذاری مناسب روی ستونهایی که جستجو میشوند انجام دهید.
- در صورت نیاز به دادههای پیچیدهتر از ستون نوع JSONB استفاده کنید تا انعطافپذیری بیشتر داشته باشید.
جمعبندی
در این مقاله یاد گرفتید چگونه با Scrapy و Postgres دادهها را ذخیره کنید: از راهاندازی دیتابیس و نصب psycopg2 گرفته تا نوشتن pipeline برای درج آیتمها و جلوگیری از تکرار با استفاده از قیدها یا بررسی قبل از درج. نکات امنیتی (متغیرهای محیطی)، بهینهسازی عملکرد (batching، pooling، ON CONFLICT) و مدیریت خطا را نیز پوشش دادیم. گام بعدی: پیادهسازی pipeline در پروژهٔ خود، آزمایش با حجم داده و افزودن logging/monitoring برای عملیات در زمان اجرا.





