خانه/مقالات/ذخیره نتایج اسکریپینگ در MySQL با Scrapy
استخراج داده
اتوماسیون
برگشت به مقاله‌ها

ذخیره نتایج اسکریپینگ در MySQL با Scrapy

ذخیره نتایج اسکریپینگ در MySQL با Scrapy
این راهنما به شکل عملی نشان می‌دهد چطور با استفاده از پایپ‌لاین‌های Scrapy خروجی‌ها را در MySQL ذخیره کنید؛ از نصب کتابخانه و نمونه Spider تا پیاده‌سازی پایپ‌لاین‌های ساده و بدون تکرار، همراه با نکات امنیتی و بهینه‌سازی برای پروژه‌های واقعی.
آسان اسکریپ آسان اسکریپ
1405-04-09

مقدمه

در این مقاله با رویکردی عملی می‌آموزیم چگونه خروجی‌های یک کرالر Scrapy را به دیتابیس MySQL منتقل کنیم. مخاطب این متن توسعه‌دهنده پایتون در سطح متوسط است که می‌خواهد پیاده‌سازی پایپ‌لاین، مدیریت اتصال، جلوگیری از تکرار و نکات امنیتی و بهینه‌سازی را گام‌به‌گام ببیند.

آیتم پایپ‌لاین‌ها در Scrapy

پایپ‌لاین‌ها زنجیره‌ای از پردازش‌ها هستند که هر آیتم پس از استخراج توسط Spider از آن عبور می‌کند. کاربردهای معمول:

  • پاک‌سازی و نرمال‌سازی فیلدها
  • اعتبارسنجی داده‌ها
  • حذف یا ثبت داده‌های تکراری
  • ذخیره‌سازی در فایل یا دیتابیس

در ادامه روی ذخیره‌سازی در MySQL تمرکز می‌کنیم و نمونه‌های عملی برای ذخیره، جلوگیری از تکرار و نکات عملی فراهم می‌کنیم.

راه‌اندازی MySQL

می‌توانید MySQL را محلی نصب کنید یا از نسخه‌ی هاست‌شده استفاده کنید. اطلاعات اتصال معمولاً شامل host، database، user و password است. مثال اتصال:

conn = mysql.connector.connect(
    host='localhost',
    user='root',
    password='123456',
    database='quotes'
)

نکته امنیتی: اطلاعات محرمانه را در کد ثابت ذخیره نکنید؛ از متغیرهای محیطی یا سیستم تنظیمات پروژه استفاده کنید.

نصب کتابخانه‌ها

برای تعامل با MySQL از mysql-connector-python یا کتابخانه‌های دیگری مثل PyMySQL و mysqlclient استفاده می‌شود. نصب با pip:

pip install mysql-connector-python

نمونه Spider و Item

مثال ساده یک Spider که نقل‌قول‌ها را استخراج می‌کند:

# spiders/quotes.py
import scrapy
from mysql_demo.items import QuoteItem

class QuotesSpider(scrapy.Spider):
    name = 'quotes'

    def start_requests(self):
        url = 'https://quotes.toscrape.com/'
        yield scrapy.Request(url, callback=self.parse)

    def parse(self, response):
        for quote in response.css('div.quote'):
            item = QuoteItem()
            item['text'] = quote.css('span.text::text').get()
            item['author'] = quote.css('small.author::text').get()
            item['tags'] = quote.css('div.tags a.tag::text').getall()
            yield item

و تعریف آیتم:

# items.py
from scrapy.item import Item, Field

class QuoteItem(Item):
    text = Field()
    tags = Field()
    author = Field()

پایپ‌لاین ساده برای ذخیره در MySQL

در pipelines.py یک پیاده‌سازی پایه می‌تواند شامل ایجاد اتصال، ایجاد جدول و درج هر آیتم باشد. در این مثال از پارامترایز کردن کوئری‌ها استفاده شده تا ریسک تزریق SQL کاهش یابد.

# pipelines.py
import mysql.connector

class MysqlDemoPipeline:
    def __init__(self):
        self.conn = mysql.connector.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='quotes'
        )
        self.cur = self.conn.cursor()
        self.cur.execute("""
        CREATE TABLE IF NOT EXISTS quotes(
            id INT NOT NULL AUTO_INCREMENT,
            content TEXT,
            tags TEXT,
            author VARCHAR(255),
            PRIMARY KEY (id)
        )
        """)

    def process_item(self, item, spider):
        # ورودی: item از Spider؛ خروجی: همان item برای ادامه جریان
        self.cur.execute(
            "INSERT INTO quotes (content, tags, author) VALUES (%s, %s, %s)",
            (item['text'], str(item['tags']), item['author'])
        )
        self.conn.commit()
        return item

    def close_spider(self, spider):
        self.cur.close()
        self.conn.close()

توضیح: process_item برای هر آیتم فراخوانی می‌شود. از متغیرهای پارامتری (%s) استفاده شده تا از تزریق جلوگیری شود. در پایان اتصال با close_spider بسته می‌شود.

فعالسازی پایپ‌لاین

# settings.py
ITEM_PIPELINES = {
   'mysql_demo.pipelines.MysqlDemoPipeline': 300,
}

با این تنظیم، وقتی Spider اجرا شود، آیتم‌ها به پایپ‌لاین فرستاده شده و در MySQL ذخیره می‌شوند.

فقط ذخیره داده‌های جدید — دو راهکار

برای جلوگیری از درج رکوردهای تکراری دو رویکرد متداول وجود دارد:

  • جستجوی رکورد قبل از درج (SELECT → INSERT)
  • استفاده از قید یکتا و درج امن (مثلاً UNIQUE + INSERT IGNORE یا مدیریت خطای تکرار)

نمونه با SELECT (ساده، اما مستعد شرایط رقابتی):

# pipelines.py (check then insert)
# روند: بررسی وجود سپس درج
self.cur.execute("SELECT 1 FROM quotes WHERE content = %s", (item['text'],))
if self.cur.fetchone():
    spider.logger.info("Item already in database: %s", item['text'])
else:
    self.cur.execute(
        "INSERT INTO quotes (content, tags, author) VALUES (%s, %s, %s)",
        (item['text'], str(item['tags']), item['author'])
    )
    self.conn.commit()
return item

نمونه بهتر با قید یکتا و هش محتوا (پیشگیری از شرایط رقابتی و افزایش پایداری):

# pipelines.py (unique hash)
import hashlib
import mysql.connector

class MySQLNoDuplicatesPipeline:
    def __init__(self):
        self.conn = mysql.connector.connect(
            host='localhost',
            user='root',
            password='your_password',
            database='quotes'
        )
        self.cur = self.conn.cursor()
        # اضافه کردن ستون content_hash با UNIQUE به جای اتکا به متن کامل
        self.cur.execute("""
        CREATE TABLE IF NOT EXISTS quotes(
            id INT NOT NULL AUTO_INCREMENT,
            content TEXT,
            content_hash VARCHAR(64) UNIQUE,
            tags TEXT,
            author VARCHAR(255),
            PRIMARY KEY (id)
        )
        """)

    def process_item(self, item, spider):
        # ورودی: item؛ خروجی: item
        h = hashlib.sha256(item['text'].encode('utf-8')).hexdigest()
        try:
            self.cur.execute(
                "INSERT INTO quotes (content, content_hash, tags, author) VALUES (%s, %s, %s, %s)",
                (item['text'], h, str(item['tags']), item['author'])
            )
            self.conn.commit()
        except mysql.connector.IntegrityError:
            # قید UNIQUE باعث جلوگیری از درج تکراری می‌شود
            spider.logger.info("Duplicate skipped by unique constraint: %s", item['text'])
        return item

نکته: استفاده از هش و قید یکتا هم از لحاظ عملکرد و هم از نظر ایمنی در سیستم‌هایی با چند فرایند/چند Worker مناسب‌تر است.

نکات عملی، مدیریت خطا و بهینه‌سازی

  • مدیریت خطا: همیشه عملیات دیتابیس را داخل بلوک try/except قرار دهید و لاگ‌گذاری مناسبی داشته باشید.
  • مدیریت اتصال: برای اجرای طولانی‌مدت یا حجم بالا از connection pooling یا کتابخانه‌هایی با پشتیبانی از reconnect خودکار استفاده کنید.
  • عملکرد: برای حجم‌های بالا از درج دسته‌ای (batch insert) و commit دوره‌ای استفاده کنید تا تعداد تراکنش‌ها کاهش یابد.
  • همزمانی: اگر چند پروسس یا چند کراولر به یک دیتابیس می‌نویسند، به کلید یکتا و طراحی تراکنشی مناسب نیاز دارید تا از تکرار و شرایط رقابتی جلوگیری شود.
  • امنیت: کوئری‌ها را پارامتری کنید و رمزها را در متغیر محیطی یا فایل تنظیمات خارج از کنترل نسخه نگهداری کنید.
  • معماری: برای مقیاس‌پذیری می‌توانید از صف پیام (صف‌بندی آیتم‌ها) و سرویس جداگانه‌ای برای نوشتن در DB استفاده کنید یا از ORM مانند SQLAlchemy برای نگهداری منطق پیچیده‌تر استفاده کنید.

جمع‌بندی

پایپ‌لاین‌های Scrapy راهی ساخت‌یافته برای پردازش و ذخیره‌سازی آیتم‌ها فراهم می‌کنند. برای پروژه‌های واقعی علاوه بر پیاده‌سازی پایه، به مدیریت خطا، جلوگیری از تکرار با قید یکتا، مدیریت امن اطلاعات اتصال و راهکارهای بهینه‌سازی عملکرد توجه کنید تا سیستم شما پایدار و قابل‌گسترش بماند.

مطالب مرتبط

مقاله‌های مرتبط