آموزش پایتون: وب اسکرپینگ سایت FiFa.com با BeautifulSoup — راهنمای کاربردی
اغلب افراد فکر میکنند علم داده به الگوریتمهای جالب یادگیری ماشین و اتومبیلهای خودران مربوط است؛ اما واقعیت چنین نیست. در این حوزه تقریباً در 80 درصد از موارد شما مشغول جستجو و پاکسازی دادهها هستید و اگر موفق باشید 20 درصد باقیمانده را به موارد فوق اختصاص میدهید. بنابراین «یافتن دادهها و بررسی آنها» یکی از مهمترین توصیههایی است که هر تازه واردی به این حوزه دریافت میکند. حال اگر بخواهید روی پروژهای کار کنید؛ اما دادههای آن روی اینترنت موجود نباشد چه باید کرد؟ هیچ کس چنین چیزهایی را به شما آموزش نمیدهد. در این مقاله قصد داریم با استفاده از پایتون و یکی از کتابخانه محبوب آن به نام BeautifulSoup دادهها را از وبسایت جام جهانی 2018 فیفا استخراج کنیم.
اغلب افراد میگویند که انگیزه در بلند مدت پایدار نیست. واقعیت این است که بهداشت فردی نیز در بلند مدت پایدار نیست و به همین دلیل است که توصیه میشود هر روز استحمام کنیم.
- زیگ زیگلار
درواقع دادههایی که ممکن است نیاز داشته باشیم همیشه به صورتی سرراست وجود ندارند. اما خبر خوب این است که در هر حال وجود دارند و در صفحههای وب مخفی شدهاند. شما صرفاً باید در این صفحهها بگردید و آنها را استخراج کنید. وب اسکرپینگ به همین منظور استفاده میشود. در این مقاله از پایتون و یکی از کتابخانههای محبوب آن به نام BeautifulSoup استفاده میکنیم تا دادهها را از وبسایت جام جهانی 2018 فیفا استخراج کنیم. این دادهها شامل اطلاعات فردی بازیکنها و آمار مربوط به کل بازیها است. پس منتظر چه هستیم؟ در ادامه این کار را آغاز میکنیم.
بخش قبلی این مجموعه مطلب آموزشی را میتوانید از طریق لینک زیر مطالعه کنید:
مرحله 0 – درک اینکه چه دادههایی باید استخراج شوند
ما به دنبال دادههایی هستیم که آنها را استخراج کنیم و از یک صفحه به صفحه دیگر میرویم. در بخش بازیکنان وبسایت فیفا با دادههای مفیدی مواجه میشویم و برخی از آنها را ذخیره میکنیم. اینک تنها کافی است این صفحه را از طریق پایتون دانلود کنیم و دادهها را از آن استخراج کنیم. منظور از دانلود کردن صفحه، این است که کد HTML صفحه و نه چیز دیگر را دانلود کنیم. پس ابتدا باید ببینیم چگونه میتوانیم یک صفحه را دانلود کنیم.
مرحله 1 – بارگذاری صفحه وب در پایتون
مطمئن هستیم که این صفحه را تا پیش از مشاهده این مقاله دستکم یک بار ملاحظه کردهاید. اگر چنین نیست میتوانید این بخش (یعنی کد منبع صفحه را) از طریق راست کلیک روی هر بخش از صفحه و انتخاب گزینه Inspect Element مشاهده کنید. دلیل این که آن را اینجا قرار دادهایم این است که وقتی دادهها را در پایتون بارگذاری میکنیم بدانید که صفحه درستی بارگذاری شده است.
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4
5#Request URL
6page = requests.get("https://www.fifa.com/worldcup/players.html")
7
8#Fetch webpage
9soup = BeautifulSoup(page.content,"html.parser")
10print(soup.prettify())
در ادامه اجزای کد فوق را توضیح میدهیم.
Requests
نخستین چیزی که برای استخراج دادههای یک صفحه نیاز داریم این است که صفحه را دانلود کنیم. ما قصد داریم این کار را با استفاده از کتابخانه requests (+) پایتون اجرا کنیم.
BeautifulSoup
از این کتابخانه (+) برای تحلیل صفحه HTML که دانلود شده است استفاده میکنیم. به بیان دیگر دادههای مورد نیاز خود را بدین ترتیب استخراج میکنیم.
با استفاده از دستور requests.get ابتدا صفحه وب را تجزیه کرده و URL را به دست میآوریم. اینک یک وهله از BeautifulSou میسازیم. این وهله را نمایش میدهیم تا مطمئن شویم که صفحه وب به طرز صحیحی بارگذاری شده است. با افزودن ()prettify. کد تمیزتر میشود و برای ما خواناتر خواهد بود. همان طور که میبینید صفحه ما به طرز صحیحی بارگذاری شده است. اینک میتوانیم دادهها را از آن استخراج کنیم.
مرحله 2 – بازیکن نخست
هرگز کدی را که درک نمیکنید، ننویسید. اسکرپینگ در زمانی که توسعهدهنده وب کار خود را به درستی انجام داده باشد بسیار آسان خواهد بود چون میتوانیم از خصوصیتهای HTML استفاده کرده و بدانیم که دادههای دقیق را میتوانیم استخراج کنیم. برای این که این حرف را بهتر متوجه شوید به تصویر زیر نگاه کنید.
همان طور که میبینید ما در صفحه پروفایل رونالدو هستیم. ما کد این صفحه را باز کردهایم و همان طور که میبینید به نام وی اشاره کردهایم. از آنجا که میخواهیم نام وی را استخراج کنیم، از این رو نوع نوشتن آن را بررسی میکنیم. همان طور که مشخص میشود یک بخش جداکننده وجود دارد و نام کلاس متفاوتی دیده میشود. اینک نگاهی به کد میاندازیم.
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4
5#Request URL
6page = requests.get("https://www.fifa.com/worldcup/players/player/201200/profile.html")
7
8#Fetch webpage
9soup = BeautifulSoup(page.content,"html.parser")
10
11#Scraping Data
12name = soup.find("div",{"class":"fi-p__name"})
13print(name)
در کد فوق ما صفحهای که پروفایل رونالدو در آن قرار دارد را استخراج کردهایم. یک وهله از کتابخانه ایجاد کردهایم و اینک متد find کتابخانه BeautifulSoup آن چه به دنبالش هستیم را یافته است. ما به یک div نیاز داریم؛ اما div-های زیادی در کد وجود دارند. اینک سؤال آن است که ما به کدام یک نیاز داریم؟ ما به آن div نیاز داریم که نام کلاس آن صرفاً fi-p__name باشد. زیرا در این بخش است که نام بازیکن نوشته شده است. ما این بخش را مییابیم.
سادهسازی خروجی
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4
5#Request URL
6page = requests.get("https://www.fifa.com/worldcup/players/player/201200/profile.html")
7
8#Fetch webpage
9soup = BeautifulSoup(page.content,"html.parser")
10
11#Scraping Data
12name = soup.find("div",{"class":"fi-p__name"}).text.replace("\n","").strip()
13print(name)
ما میتوانیم دادهها را در قالب متنی مورد نیاز خود ذخیره کنیم. به همین دلیل است که عبارت text. را به انتهای آن اضافه کردهایم. بدین ترتیب دادهها به شکل زیر درمیآیند:
هنوز کار ما به پایان نرسیده است و چند کاراکتر «n\» در متن ما وجود دارد که باید آنها را نیز حذف کنیم. بنابراین آنها را با مقدار خالی جایگزین میکنیم و بدین ترتیب خروجی زیر به دست میآید:
یک کار دیگر که باید انجام دهیم حذف کردن فاصلههای ابتدایی و انتهایی است. به این منظور از متد()strip استفاده میکنیم تا خروجی نهایی به شکل زیر در بیاید:
اینک آنچه را میخواستیم به دست آوردهایم. ما نام یک بازیکن را واکشی کردیم. با استفاده از همین رویه میتوانیم جزئیات دیگری را که در صفحه وب مشاهده میکنیم مانند قد، کشور، نقش و تعداد گلها را نیز به دست آوریم.
مرحله 3 – همه دادههای بازیکن اول
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4
5#Request URL
6page = requests.get("https://www.fifa.com/worldcup/players/player/201200/profile.html")
7
8#Fetch webpage
9soup = BeautifulSoup(page.content,"html.parser")
10
11#Scraping Data
12#Name #Country #Role #Age #Height #International Caps #International Goals
13player_name = soup.find("div",{"class":"fi-p__name"}).text.replace("\n","").strip()
14player_country = soup.find("div",{"class":"fi-p__country"}).text.replace("\n","").strip()
15player_role = soup.find("div",{"class":"fi-p__role"}).text.replace("\n","").strip()
16player_age = soup.find("div",{"class":"fi-p__profile-number__number"}).text.replace("\n","").strip()
17player_height = soup.find_all("div",{"class":"fi-p__profile-number__number"})[1].text.replace("\n","").strip()
18player_int_caps = soup.find_all("div",{"class":"fi-p__profile-number__number"})[2].text.replace("\n","").strip()
19player_int_goals = soup.find_all("div",{"class":"fi-p__profile-number__number"})[3].text.replace("\n","").strip()
20
21print(player_name,"\n",player_country,"\n",player_role,"\n",player_age,"years \n",player_height,"\n",player_int_caps,"caps \n",player_int_goals,"goals")
هر پارامتر کلاس خاص خود را دارد و کار ما آسان میشود. با این حال مقادیر قد، تعداد بازیهای بینالمللی و تعداد گلها چنین نیستند. البته جای نگرانی نیست؛ ما از متد find_all استفاده میکنیم و سپس هر کدام از آنها را با شماره اندیس ترتیبیشان نمایش میدهیم. میبینید که این کار نیز ساده شد. خروجی چیزی مانند زیر خواهد بود:
مرحله 4 – همه دادههای 376 بازیکن
در بخش قبل موفق شدیم همه دادههای یک بازیکن را به دست آوریم؛ اما این کلید به دست آوردن دادههای همه بازیکنها است. پروفایل همه بازیکنان در صفحههای وب مختلف قرار دارد. ما باید همه آنها را تنها با یک اسکریپت به دست آوریم و قصد نداریم برای هر بازیکن منفرد یک اسکریپت مجزا بنویسیم. بدین منظور باید یک الگو داشته باشیم تا بتوانیم URL همه بازیکنان را به صورت همزمان به دست آوریم.
همان طور که میبینید URL صفحه وب پروفایل رونالدو به صورت زیر است:
https://fifa.com/worldcup/players/player/201200/profile.html
اینک باید به دنبال بخشهایی باشیم که در آدرس صفحه وب پروفایل همه بازیکنان مشترک است. همان طور که میبینید همه بخشهای URL به جز شناسه بازیکن (برای رونالدو به صورت 201200 است) مشترک است. بنابراین پیش از اسکرپینگ همه دادههای دیگر باید ID-های بازیکنان را به دست آوریم. این کار به همان طریقی که نام استخراج شد، صورت میگیرد؛ اما این بار از یک حلقه استفاده میکنیم.
اینک زمانی که کد منبع را بررسی میکنیم با یک لینک (+) مواجه میشویم که ID همه بازیکنان در آن ذکر شده است. میتوانیم از این صفحه استفاده کنیم و ID همه بازیکنان را به دست آوریم.
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4import pandas
5from collections import OrderedDict
6
7#Fetch data of Player's ID
8player_ids = pandas.read_csv("player_ids.csv")
9ids = player_ids["Ids"]
10
11#Prive a base url and an empty list
12base_url = "https://www.fifa.com/worldcup/players/player/"
13player_list = []
14
15#Iterate to scrap data of players from fifa.com
16for pages in ids:
17 #Using OrderedDict instead of Dict (See explaination)
18 d=OrderedDict()
19 #Fetching URLs one by one
20 print(base_url+str(pages)+"/profile.html")
21 request = requests.get(base_url+str(pages)+"/profile.html")
22 #Data processing
23 content = request.content
24 soup = BeautifulSoup(content,"html.parser")
25 #Scraping Data
26 #Name #Country #Role #Age #Height #International Caps #International Goals
27 d['Name'] = soup.find("div",{"class":"fi-p__name"}).text.replace("\n","").strip()
28 print(d['Name'])
29 d['Country'] = soup.find("div",{"class":"fi-p__country"}).text.replace("\n","").strip()
30 d['Role'] = soup.find("div",{"class":"fi-p__role"}).text.replace("\n","").strip()
31 d['Age'] = soup.find("div",{"class":"fi-p__profile-number__number"}).text.replace("\n","").strip()
32 d['Height(cm)'] = soup.find_all("div",{"class":"fi-p__profile-number__number"})[1].text.replace("\n","").strip()
33 d['International Caps'] = soup.find_all("div",{"class":"fi-p__profile-number__number"})[2].text.replace("\n","").strip()
34 d['International Goals'] = soup.find_all("div",{"class":"fi-p__profile-number__number"})[3].text.replace("\n","").strip()
35
36#Append dictionary to list
37player_list.append(d)
38#Create a pandas DataFrame to store data and save it to .csv
39df = pandas.DataFrame(player_list)
40df.to_csv('Players_info.csv', index = False)
41print("Success \n")
وبسایت را بررسی کردیم و در مجموع 736 بازیکن وجود دارد. از این رو یک حلقه برای این تعداد اجرا میکنیم. در این کد به جای div یک a وجود دارد که تگ anchor است؛ اما کارکرد آن مانند روش قبلی است و به دنبال کلاس مورد نظر گشته و آن را مییابیم. یک لیست خالی در همان ابتدا ایجاد میکنیم و سپس در هر تکرار ID-ها را به این لیست الحاق میکنیم. در نهایت یک قاب داده (dataframe) میسازیم که این ID-ها را در آن ذخیره کرده و در یک فایل CSV اکسپورت میکنیم.
واکشی همه دادههای تمامی بازیکنان
اینک که ID-ها را در اختیار داریم و میدانیم که چگونه هر پارامتر را واکشی کنیم، کافی است این ID-ها را ارسال کنیم و همه آنها را در طی تکرار حلقه اجرا کنیم. به کد زیر توجه کنید:
1#Import libraries
2import requests
3from bs4 import BeautifulSoup
4import pandas
5
6#Empty list to store data
7id_list = []
8
9#Fetching URL
10request = requests.get("https://www.fifa.com/worldcup/players/_libraries/byposition/[id]/_players-list")
11soup = BeautifulSoup(request.content,"html.parser")
12
13#Iterate to find all IDs
14for ids in range(0,736):
15 all = soup.find_all("a","fi-p--link")[ids]
16 id_list.append(all['data-player-id'])
17
18#Data Frame to store scrapped data
19df = pandas.DataFrame({
20"Ids":id_list
21})
22df.to_csv('player_ids.csv', index = False)
23print(df,"\n Success")
ما یک URL مبنا داریم و از این رو وقتی روی ID-های مختلف حلقهای تعریف کنیم، تنها ID تغییر مییابد و دادهها را از همه URL-ها دریافت میکنیم. یک نکته دیگر این است که به جای لیست از دیکشنری استفاده میکنیم، زیرا پارامترهای چندگانه دارد. به طور خاص «دیکشنری مرتب» (Ordered Dictionary) مناسب است، زیرا دادهها به همان ترتیبی که واکشی میشوند در آن ذخیره میشوند. اگر این دیکشنری را به یک لیست الحاق کنیم یک دیتافریم به دست میآید. دادهها را در یک فایل CSV ذخیره میکنیم تا برای مراجعات بعدی به آنها دسترسی داشته باشیم. ما در زمان اجرای اسکریپت تنها نام بازیکن را نمایش میدهیم، زیرا بدین ترتیب میتوانیم مطمئن باشیم که دادهها به طرز صحیحی واکشی شدهاند.
به این ترتیب ما موفق شدیم که این کار را انجام دهیم. دادههایی که بدین ترتیب واکشی شدند را میتوانید در این لینک (+) مشاهده کنید.
مرحله 5 – آمار
ما به این مقدار اکتفا نمیکنیم. در صفحه بازیکنها آمار فوق را نیز که مربوط به کل جام جهانی است پیدا کردیم و به طور خودکار مغز ما اعلام میکند که «گویا صفحه دیگری وجود دارد که باید اسکرپ کنیم!». ما اسکرپینگ این صفحه را به عنوان یک تمرین به شما واگذار میکنیم تا مهارتهایی که در این مقاله آموختید را تمرین کنید.
در هر حال اگر موفق به این کار نشدید، میتوانید نتیجه مربوط را در این لینک (+) ملاحظه کنید. اما این کد یک اشکالی دارد. شما میتوانید این اشکال را پیدا کنید، یا این که اسکرپینگ را از طریق باز کردن یک درخواست pull روی ریپازیتوری کامل کنید. ما میدانیم که کد به طرق مختلفی میتواند بهینهسازی شود، اما در حال حاضر نقطه تمرکز ما یادگیری است. مهم نیست که برنامه شما از نظر پیچیدگی زمانی و فضایی چگونه اجرا میشود؛ اگر پاسخ صحیح را در دست داشته باشید، بدانید که در مسیر صحیحی هستید.
سخن پایانی
در این مقاله با دو کتابخانه جدید آشنا شدیم و شیوه استفاده از آنها برای دانلود صفحه وب و اسکرپ کردن دادهها از آن صفحهها را آموختیم. در واقع شما در صورتی همین یک مهارت را داشته باشید در مصاحبههای شغلی نسبت به رقبای خود بسیار بالاتر میایستید. هر گونه دیدگاه یا پیشنهاد خود را میتوانید در بخش نظرات این نوشته با ما در میان بگذارید.
برای مطالعه قسمت بعدی این مجموعه مطلب آموزشی میتوانید روی لینک زیر کلیک کنید:
اگر این مطلب برای شما مفید بوده است؛ آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزش های برنامه نویسی پایتون
- آموزش اصول و روش های داده کاوی (Data Mining)
- مجموعه آموزشهای برنامهنویسی
- آموزش یادگیری ماشین (Machine Learning) با پایتون (Python)
- گنجینه آموزش های یادگیری ماشین و داده کاوی
==
چرا من هر چقدر اجرا ميكنم كد شما رو None رو به من ميده؟