مقدمه
در این مقاله با رویکردی عملی میآموزیم چگونه خروجیهای یک کرالر 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 راهی ساختیافته برای پردازش و ذخیرهسازی آیتمها فراهم میکنند. برای پروژههای واقعی علاوه بر پیادهسازی پایه، به مدیریت خطا، جلوگیری از تکرار با قید یکتا، مدیریت امن اطلاعات اتصال و راهکارهای بهینهسازی عملکرد توجه کنید تا سیستم شما پایدار و قابلگسترش بماند.





