سلام سلام! ?
آرمین هستم از استودیو کاغذ!
خیلی وقت میشه که از ساخت اکانت ویرگول کاغذ میگذره اما فرصتِ مناسب برای فعالیت و نوشتن پیش نمیومد، راستش هنوز حتی فرصت مناسب برای طراحی وبسایت شرکتیمون هم پیش نیومده ☹️
اما خب میخوام با یه مقاله خفن شروع کنم :)
چند روزی بود که بابت انجام یک پروژه فروشگاهی و تست قسمت های مختلفش نیاز به یک حجم بالایی محصول با اطلاعات واقعی داشتم و خب ایجاد کردن محصولات به صورت دستی، به طور میانگین به ازای هر محصول حدود 12 دقیقه زمان میبرد بخاطر همین دنبال یک راه بهینه و مکانیزه تر گشتم و از اونجایی که هیچ منبع درست حسابی برای این مورد پیدا نکردم دوست دارم تجربه ای که توی این زمینه بدست آوردم رو باهاتون به اشتراک بزارم! ❤️
⚠️ هشدار: این مقاله به صورت محاوره ای نوشته شده، کمی چاشنی شوخ طبعی داره و از لغات و تعریفات آکادمیک استفاده نشده!
? فایل ویدیوئی این مقاله رو براتون ضبط میکنم و لینکشو توی کامنت ها میزارم ?
درصورتی که با مفهوم وب اسپایدر و وب اسکرپر آشنا هستی میتونی این بخش رو رد کنی! ?
خب قبل از هرچیزی باید بدونید که وب اسپایدر "Web Spider"، وب کراولر "Web Crawler" یا خیلی کوتاه، خزنده؛ همشون به یک موضوع اشاره میکنن و مکانیزم یکسانی دارند که باهم درموردشون صحبت میکنیم!
یک مورد دیگه بین این مفاهیم وجود داره به نام وب اسکرپر "Web Scraper" که تقریبا میشه گفت مکمل وب اسپایدره!
"خیلی عامیانه" وب اسپایدر که برای ساده تر شدن از این به بعد بهش میگیم خزنده نرم افزاریه که با یک منطق از پیش مشخص شده توی صفحه یا صفحات مشخصی از وب شروع به خزیدن میکنه و عملیات های مشخصی رو با توجه به منطقی که براش تعریف شده انجام میده!
*** به عملیاتی که توسط خزنده انجام میشه خزیدن میگن که همون Crawling میشه! ***
وب اسکرپر که از این به بعد بهش میگیم اسکرپر دقیقا همون خزنده ای هست که بعد از خزیدن در صفحات، به منظور مشخصی از اطلاعات صفحات استفاده میکنه!
ساده تر بگم؛ وقتی یک خزنده به قصد جمع آوری اطلاعات وارد صفحات وب میشه به یک اسکرپر تبدیل میشه :)
شما وقتی مرورگرتون رو باز میکنید، میاید توی سایت ویرگول و بین مقالات ما میچرخید، ❤️میکنید و برامون ? میزارید دقیقا دارید عملیات خزیدن رو انجام میدید و مرورگرتون نقش خزنده رو بازی میکنه!
حالا وقتی که شما میاید و مقالات مارو به قصد اشتراک گذاری و تعریف و تمجید کپی میکنید یا لینکشو توی شبکه های اجتماعیتون به اشتراک میگذارید، دارید عملیات اسکرپینگ رو در نقش یک اسکرپر انجام میدید! ?
خب حالا که با مفهوم و نحوه عملکرد خزنده و اسکرپر آشنا شدید، پس بریم یکیشو بنویسیم! ?♂️
الان ما میدونیم که خزنده وب چیه و چیکار میکنه پس میتونیم توی هر زبان برنامه نویسی که راحت تر هستیم، یک خزنده وب ایجاد کنیم اما توی این مقاله با PHP این کار رو انجام میدیم و از ابزار Composer یه کمک کوچولو میگیریم!
⚠️ اگه نمیدونید کامپوزر چیه و چیکار میکنه، درموردش سرچ کنید و بعد ادامه مقاله رو بخونید
? البته سعی میکنم خیلی زود توی یک مقاله درموردش براتون بگم :)
دو روشی که من خودم توی زبان PHP استفاده کردم روی سعی میکنم تا حد ممکن به صورت ساده بهتون بگم!
روش اول: با استفاده از توابع داخلی PHP
روش دوم: با استفاده از کتابخونه محبوب Spatie Crawler
** البته که در هر دو حالت از ابزار DomCrawler استفاده میشه و خیلی کار راه بندازه :)
نکــــتـــه » روش دوم رو توی یک مقاله دیگه خدمتتون عرض خواهم کرد ?
نکــــتـــه » قطعا میشه به کلی راه و روش دیگه این موضوع رو پیاده سازی کرد ولی خب من این دو روش رو مناسب تر دیدم...
قراره یک خزنده ایجاد کنیم که یک صفحه مشخص از دیجی کالا رو بررسی کنه و تمام محصولاتش رو به یک کانال تلگرامی ارسال کنه
رسیدیم به جای جذاب ماجرا!
در مرحله اول یک فولدر با هر اسمی که دوست دارید ایجاد کنید، مثلا Crawler!
سپس توی فولدرتون، CMD رو باز کنید و دستور زیر رو برای init کردن Composer بزنید!
composer init
حالا اطلاعات خواسته شده رو پر کنید و یا تا آخر، بدون وارد کردن اطلاعات، فقط Enter بزنید!! #نصب_ایرانی_طور
خب الان نیاز به ابزار Dom Crawler داریم پس برای نصبش دستور زیر رو وارد کنید
composer require symfony/dom-crawler
خب حالا برای اجرا شدن کد هامون نیاز به یک فایل داریم، پس یک فایل به اسم index.php ایجاد کنید!
با فرض اینکه به PHP تسلط کافی رو دارید و یا با مباحث برنامه نویسی مثل کلاس ها و نیم اسپیس ها و ... آشنا هستید، توی فایلتون کد های زیر رو بنویسید!
⚠️ نکــــتـــه » برای ساده شدن کار از معماری ها و اصول کلین کد پیروی نمیکنیم و این مورد به عهده خودتونه!
خط اول فایل های autoload ایجاد شده توسط composer را به فایل ها تزریق میکند!
سپس یک کلاس به نام DigiKalaScraper ایجاد کردیم!
حالا باید یک تابع به نام crawl ایجاد کنیم و کد های خزندهمون رو داخلش بنویسیم!
تابع crawl یک ورودی به نام url میگیره که آدرس صفحه ای هست که قراره ازش اطلاعات بگیریم!
بر فرض ما میخوایم اطلاعات محصولات این صفحه رو از دیجیکالا دریافت کنیم پس دقیقا قبل از اسم کلاسمون و بعد از کد "require" باید یک نمونه از کلاسمون ایجاد کنیم و تابع خزیدن "crawl" رو داخلش صدا بزنیم
کدهای زیر رو برای این کار بنویسید!
$digiCrawler = new DigiKalaScraper(); $digiCrawler->crawl('https://www.digikala.com/search/category-mens-apparel/');
⚠️ نکــــتـــه » از این به بعد کدهایی که گفته میشه رو داخل تابع crawl مینویسیم!
حالا ما توی تابع crawl به لینک صفحه از طریق ورودی url دسترسی داریم و میخوام محتوای صفحه رو دریافت کنیم!
برای این کار میتونیم از تابع file_get_contents استفاده کنیم که باعث میشه سورس پیج لینک رو دریافت کنه و بهمون برگردونه!
کد زیر صفحه رو برامون دریافت میکنه و توی متغیر page میریزه!
$page = file_get_contents($url);
حالا باید یک نمونه جدید از DomCrawler ایجاد کنید و محتوای سورس پیج صفحه رو که توی متغیر page داریم، بهش پاس بدیم تا بتونیم روش عملیات های مد نظر رو انجام بدیم!
$pageCrawler = new Symfony\Component\DomCrawler\Crawler((string) $page);
تا اینجای کار موفق شدیم نرم افزاری بنویسیم که اطلاعات یک صفحه دلخواه رو دریافت میکنه و آماده عملیات میکنه؛ کدمون تا اینجا به این صورت شده:
برای دریافت محصولات در دیجی کالا باید یه سر به فرمت HTML سایتشون بزنیم و ببینیم محصولات رو چطوری تعریف کردن!
دقت کنید که ما باید توی اون صفحه، تمام محصولات رو پیدا کنیم و بعد، وارد هر صفحه محصول بشیم و اطلاعاتشونو برداریم پس توی این مرحله لینک صفحه محصولات برامون مهمه!
** برای پیدا کردن لینک ها کافیه روی یکی از کارت های محصولات inspect element کنید و میبینید که:
همه محصولات یک div دارن که کلاس c-product-box داره و داخل این div یک تگ a وجود داره که کلاس زیر رو داره
c-product-box__img js-url js-product-url js-carousel-ga-product-box
و اتریبیوت href این تگ، میشه لینک محصول که به این صورت هست
/product/dkp-379576/کفش-راحتی-طبی-نهرین-مدل-دکتر-شول-1116
? خب یه شعبده بازی بهتون بگم :)
میتونید لینک محصولات رو به این صورت هم داشته باشید!
/product/dkp-379576
و از شر اون قسمت فارسی لینک خلاص بشید!
در صفحه مشخص شده، تمام تگ های a که کلاس فوق رو دارن، لینک محصول هستند و با دسترسی به اتریبیوت href اون تگ میتونیم بریم صفحه محصول!
پس بریم همه این تگ هارو با خزندهمون دریافت کنیم!
توی ابزار DomCrawler یه متد داریم به اسم filter که دقیقا کارش اینه که میاد یه سلکتور از ما میگیره و تمام المنت هایی که با اون سلکتور مچ میشن رو بهمون برمیگردونه!
ما نیاز داریم که همه همه تگ های a با یک کلاس مشخص رو بگیریم!
نحوه سلکتور نویسی برای DomCrawler به این صورته
TagName[attribute="value"]
مثلا اگه بخوایم تمام تگ های metaـی که nameـشون برابر با title هست رو بگیریم کافیه به این صورت عمل کنیم
meta[name="title"]
برگردیم سر نیازمون و تگ های a با کلاس مربوطه رو بگیریم و توی یک متغیر به اسم productLinks بریزیم!
اگه یکم برگردید بالا میبینید که DomCrawlerـمون رو توی یک متغیر به اسم pageCrawler ریختیمش پس الان به این صورت باید عمل کنیم
$productLinks = $pageCrawler->filter('a[class="c-product-box__img js-url js-product-url js-carousel-ga-product-box"]');
حالا تمام لینک های محصولات رو توی متغیر productLinks داریم و میتونیم عملیات دلخواهمونو روشون انجام بدیم!
همونطور که گفتم باید اطلاعات محصولات رو بخونیم پس باید یک حلقه روی تمام لینک ها اجرا کنیم و تک تک صفحات محصولات رو باز کنیم تا بعد بتونیم توی هر صفحه اطلاعات مد نظرمون رو دریافت کنیم!
از حلقه foreach استفاده میکنیم و به این صورت عمل میکنیم
foreach($productLinks as $productLink) { //.... }
مسئله ای که هست اینه که لینک های دریافت شده توسط pageCrawler به صورت DomElement به ما برمیگرده و نمیتونیم مثل آرایه یا موارد دیگه باهاش رفتار کنیم اما خب جای نگرانی نداره، کار با DomElement خیلی سادست :)
تمام productLink هایی که توی حلقه داریم یک DomElement هست!
ما میتونیم با DomElement از تابع getAttribute برای گرفتن اتریبیوت href یا بقیه اتریبیوت های المنت مربوطه استفاده کنیم!
پس با این وجود ما برای گرفتن href تگ هامون که لینک محصولات میشه باید به این صورت عمل کنیم
$_link = $productLink->getAttribute('href');
حالا لینک محصول رو از تگ a بیرون کشیدیم و توی متغیر link_ داریمش!
اگه یادتون باشه لینک محصولات توی دیجی کالا به این صورت بود
/product/dkp-379576/کفش-راحتی-طبی-نهرین-مدل-دکتر-شول-1116
و ما فقط این قسمت رو نیاز داشتیم
/product/dkp-379576
پس به کمک تابع explode میایم لینک رو بر اساس / منفجر میکنیم و صرفا این قسمت که ایدی محصول هست رو برمیداریم!
dkp-379576
پ.ن : product رو به این دلیل نیاز نداریم که توی همه محصولات ثابته!
کدمون به این صورت میشه:
$productSKU = explode('/', $_link)[2];
و جهت ایجاد لینک صحیح و کامل کد زیر رو مینویسیم:
$productUrl = 'https://www.digikala.com/product/' . $productSKU;
هوراا :)) لینک صفحه محصول رو هم به طور کامل گرفتیم!
تا اینجا کدمون به این صورت شده :
خب حالا که لینک صفحه محصول رو داریم و توی یک حلقه هستیم که تک تک محصولات رو پیمایش میکنه پس بریم سراغ دریافت اطلاعات صفحه محصول!
دقیقا مثل قبل که اطلاعات صفحه اصلی رو دریافت کردیم و براش DomCrawler ایجاد کردیم عمل میکنیم بخاطر همین زیاد توضیح نمیدم درموردش!
$productPage = file_get_contents($productUrl); $productCrawler = new Symfony\Component\DomCrawler\Crawler((string) $productPage );
حالا ما اطلاعات صفحه محصول رو هم داریم و میتونیم باهاش هرکاری انجام بدیم :)
پس بیایم ببینیم از محصول چی نیاز داریم!
مثل بخش ششم، باید ببینیم که چی از محصول نیاز داریم و چطوری میتونیم پیداش کنیم!
فرض رو بر این میگیریم که این موارد رو نیاز داریم
خب این محصول رو باز کنیم بریم تو صفحش ببینیم چخبره!
اسم محصول رو از طریق تگ meta که اتریبیوت propertyـش برابر با og:title هست میتونیم بگیریم!
وضعیت موجودی محصول رو هم میتونیم از طریق یک متا دیگه بگیریم که اتریبیوت propertyـش برابر با product:availability هستش!
قیمت محصول رو هم از طریق این div میتونیم بگیریم
لینک محصول رو هم که از قبل داریم!
توی متغیر productCrawler دسترسی به اطلاعات صفحه رو داشتیم و الان فقط کافیه موارد مورد نیازمون رو با استفاده از متدهای DomCrawler فیلتر و مقدارشونو دریافت کنیم پس بریم تو کارش :)
یادتونه که فیلتر نویسی چطوری بود!
اسم محصول رو اینطوری میتونیم بگیریم:
$persianName = trim($productCrawler->filter('meta[property="og:title"]');
اما اینجا بهمون یک DomElement برمیگرده و باید یکم تلاش کنیم برای دسترسی به اطلاعاتش!
با متد first میتونیم به اولین element دسترسی پیدا کنیم
با متد getNode اولین نود رو بگیریم، حالا که به متاتگ مد نظرمون رسیدیم
با getAttribute میایم اتریبیوت مد نظرمونو میگیریم که توی متا تگ ها مقدارشون تو content قرار میگیره!
پس کدمون به این صورت میشه :
$persianName = trim($productCrawler->filter('meta[property="og:title"]') ->first() ->getNode(0) ->getAttribute('content'));
خب الان اسم رو گرفتیم!
مثل دریافت نام، همین کار رو برای دریافت موجودی هم میکنیم!
$inStock = $productCrawler->filter('meta[property="product:availability"]') ->first() ->getNode(0) ->getAttribute('content') == 'in stock';
برای دریافت قیمت هم کد زیر رو میزنیم
$purePrice = trim($productCrawler->filter('div[class="c-product__seller-price-pure js-price-value"]') ->first() ->text());
تابع text برای دریافت محتوای داخل تگ انتخاب شده استفاده میشه!
حالا که اطلاعات مد نظرمون رو گرفتیم بریم سراغ قسمت شیرین و جذاب کار که حسابی میتونید پٌزِشو به دوستاتون بدید :))
ارسال اطلاعات محصول به یک کانال تلگرامی توسط یک ربات API
اول یک فرمت خوب برای متنی که قراره به کانال ارسال بشه مشخص کنیم؛ مثلا :
? پیراهن مردانه مدل rm9941
➖➖➖➖➖➖➖➖
? ۸۴,۰۰۰ تومان
✅ موجود است
➖➖➖➖➖➖➖➖
? خرید محصول
➖➖➖➖➖➖➖➖
? Powered By Hex
خب حالا باید جزئیات رو توی این فرمت جا بدیم
اگه یادتون باشه، وضعیت موجودی محصول به صورت true یا false برمیگرده پس میتونیم یه فرمت بدیم بهش به این صورت
$productStock = $inStock ? '✅ موجود است' : '❌ موجود نیست';
و حالا بریم سراغ بقیه متن
$finalText = urlencode("? $persianName ➖➖➖➖➖➖➖➖ ? $purePrice تومان $productStock ➖➖➖➖➖➖➖➖ <a href='$productUrl'>? خرید محصول</a> ➖➖➖➖➖➖➖➖ ? Powered By <a href='tg://user?id=2067899637'>Hex</a> ");
پ.ن: بخاطر اینکه متن فارسی توی لینک به مشکل میخوره از تابع urlencode استفاده میکنیم!
⚠️ به دلیل طولانی شدن محتوای این مقاله، نمیتونم درمورد ربات تلگرام اینجا توضیح بدم و توی یک مقاله دیگه بهتون توضیحات لازم رو میدم!
پس فرض بر اینه که به ایجاد ربات های تلگرام آشنا هستید!
کار های زیر رو انجام بدید:
در انتها برای ارسال به تلگرام کد زیر رو به آخر کد های crawler اضافه کنید :)
file_get_contents('https://api.telegram.org/bot<TOKEN>/sendMessage?chat_id=-<CHAT-ID>&text=' . $finalText . '&parse_mode=HTML');
بجای <TOKEN> ، توکن رباتتونو بزارید.
بجای <CHAT-ID>، یوزر ایدی چنلتونو بزارید.
خب فقط مونده یه کارِ دیگه!
یه لاگ بزارید که متوجه بشید محصولی Crawl شده یا نه!
و یه فاصله زمانی بندازید که ایپیتون بلاک نشه
پس دو خط کد زیر رو به انتهای کد های تابع crawl اضافه کنید
echo "[+] Crawled".PHP_EOL; sleep(rand(5,10));
توی فولدر پروژتون CMD رو باز کنید و دستور زیر رو بنویسید
php index.php
از خزنده باحالتون لذت ببرید!
پویا قربان خانی، یکی از دوستای عزیز و چندین و چند ساله ما هستش که به طور تخصصی توی داده کاوی و هوش مصنوعی کار میکنه!
اگه برای کسب و کارتون نیاز به یه منیجر محتوا دارید و میخواید محتوای بسیار حرفه و سئو شده تولید کنید و هزینه و زمان کمتری صرف کنید، پویا و تیمشون همشه در کنارتون هستند :)
ممنون میشم که با لایک، کامنت و اشتراک گذاری این مقاله بهمون انگیزه نوشتن رو میدید :)
براتون آرزوی موفقیت و سلامتی دارم