واکاوی اطلاعات از دیجی‌کالا - روش اول

سلام سلام! ?آرمین هستم از استودیو کاغذ!خیلی وقت میشه که از ساخت اکانت ویرگول کاغذ میگذره اما فرصتِ مناسب برای فعالیت و نوشتن پیش نمیومد، راستش هنوز حتی فرصت مناسب برای طراحی وبسایت شرکتیمون هم پیش نیومده ☹️اما خب میخوام با یه مقاله خفن شروع کنم :)چند روزی بود که بابت انجام یک پروژه فروشگاهی و تست قسمت های مختلفش نیاز به یک حجم بالایی محصول با اطلاعات واقعی داشتم و خب ایجاد کردن محصولات به صورت دستی، به طور میانگین به ازای هر محصول حدود 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/');? بخش پنجم - دریافت اطلاعات صفحه و ساخت DomCrawler⚠️ نکــــتـــه » از این به بعد کدهایی که گفته میشه رو داخل تابع 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=&quotvalue&quot]مثلا اگه بخوایم تمام تگ های metaـی که nameـشون برابر با title هست رو بگیریم کافیه به این صورت عمل کنیمmeta[name=&quottitle&quot]برگردیم سر نیازمون و تگ های a با کلاس مربوطه رو بگیریم و توی یک متغیر به اسم productLinks بریزیم!اگه یکم برگردید بالا میبینید که DomCrawlerـمون رو توی یک متغیر به اسم pageCrawler ریختیمش پس الان به این صورت باید عمل کنیم$productLinks = $pageCrawler->filter('a[class=&quotc-product-box__img js-url js-product-url js-carousel-ga-product-box&quot]');حالا تمام لینک های محصولات رو توی متغیر 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;هوراا :)) لینک صفحه محصول رو هم به طور کامل گرفتیم!تا اینجا کدمون به این صورت شده :⚠️ نکــــتـــه » اسکرین شات صرفا کدهای تابع crawl رو شامل میشه? بخش دهم - دریافت اطلاعات صفحه محصولخب حالا که لینک صفحه محصول رو داریم و توی یک حلقه هستیم که تک تک محصولات رو پیمایش میکنه پس بریم سراغ دریافت اطلاعات صفحه محصول!دقیقا مثل قبل که اطلاعات صفحه اصلی رو دریافت کردیم و براش 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=&quotog:title&quot]');اما اینجا بهمون یک DomElement برمیگرده و باید یکم تلاش کنیم برای دسترسی به اطلاعاتش!با متد first میتونیم به اولین element دسترسی پیدا کنیمبا متد getNode اولین نود رو بگیریم، حالا که به متاتگ مد نظرمون رسیدیمبا getAttribute میایم اتریبیوت مد نظرمونو میگیریم که توی متا تگ ها مقدارشون تو content قرار میگیره!پس کدمون به این صورت میشه :$persianName = trim($productCrawler->filter('meta[property=&quotog:title&quot]') ->first() ->getNode(0) ->getAttribute('content'));خب الان اسم رو گرفتیم!? دریافت وضعیت موجودی محصولمثل دریافت نام، همین کار رو برای دریافت موجودی هم میکنیم!$inStock = $productCrawler->filter('meta[property=&quotproduct:availability&quot]') ->first() ->getNode(0) ->getAttribute('content') == 'in stock';? دریافت قیمت محصولبرای دریافت قیمت هم کد زیر رو میزنیم$purePrice = trim($productCrawler->filter('div[class=&quotc-product__seller-price-pure js-price-value&quot]') ->first() ->text());تابع text برای دریافت محتوای داخل تگ انتخاب شده استفاده میشه!⚠️ نکــــتـــه » اسکرین شات صرفا کدهای تابع crawl رو شامل میشه? بخش سیزدهم - نرمالیزیشن اطلاعات محصولحالا که اطلاعات مد نظرمون رو گرفتیم بریم سراغ قسمت شیرین و جذاب کار که حسابی میتونید پٌزِشو به دوستاتون بدید :))ارسال اطلاعات محصول به یک کانال تلگرامی توسط یک ربات APIاول یک فرمت خوب برای متنی که قراره به کانال ارسال بشه مشخص کنیم؛ مثلا :? پیراهن مردانه مدل rm9941➖➖➖➖➖➖➖➖? ۸۴,۰۰۰ تومان✅ موجود است➖➖➖➖➖➖➖➖? خرید محصول➖➖➖➖➖➖➖➖? Powered By Hexخب حالا باید جزئیات رو توی این فرمت جا بدیماگه یادتون باشه، وضعیت موجودی محصول به صورت true یا false برمیگرده پس میتونیم یه فرمت بدیم بهش به این صورت$productStock = $inStock ? '✅ موجود است' : '❌ موجود نیست';و حالا بریم سراغ بقیه متن$finalText = urlencode(&quot? $persianName ➖➖➖➖➖➖➖➖ ? $purePrice تومان $productStock ➖➖➖➖➖➖➖➖ <a href='$productUrl'>? خرید محصول</a> ➖➖➖➖➖➖➖➖ ? Powered By <a href='tg://user?id=2067899637'>Hex</a> &quot);پ.ن: بخاطر اینکه متن فارسی توی لینک به مشکل میخوره از تابع 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 &quot[+] Crawled&quot.PHP_EOL; sleep(rand(5,10));? بخش شانزدهم - اجراااااا....توی فولدر پروژتون CMD رو باز کنید و دستور زیر رو بنویسیدphp index.phpاز خزنده باحالتون لذت ببرید!? معرفی یک دوست، یک کسب و کارپویا قربان خانی، یکی از دوستای عزیز و چندین و چند ساله ما هستش که به طور تخصصی توی داده کاوی و هوش مصنوعی کار میکنه!اگه برای کسب و کارتون نیاز به یه منیجر محتوا دارید و میخواید محتوای بسیار حرفه و سئو شده تولید کنید و هزینه و زمان کمتری صرف کنید، پویا و تیمشون همشه در کنارتون هستند :)☕️ سخن پایانیممنون میشم که با لایک، کامنت و اشتراک گذاری این مقاله بهمون انگیزه نوشتن رو میدید :)براتون آرزوی موفقیت و سلامتی دارم