آیا تا به حال شده که بخواهید یک نوع خاص داده را از یک صفحه ویکیپدیا دانلود کنید؟ مثلا فقط یک بخش از یک صفحه را؟ یا در مثال من، یک جدول!
(لطفا مستقیما به بخش نحوه کار با API ویکیپدیا رجوع کنید و از خواندن مهملاتی که نوشتم اجتناب کنید)
چند وقت پیش، نیاز داشتم که یک بررسی آماری انجام دهم. برای همین در اینترنت به دنبال دادههای خام بودم و نه آنهایی که توسط یک به اصطلاح خبرگزاری منتشر شدهاند.
بالاخره توانستم یک جدول از وقایع را در ویکیپدیا پیدا کنم. اما مشکل آنجا بود که فهمیدم شمارش با دست(!) راهحل خوبی نیست.
پس شروع کردم به تحقیق که چگونه این کار را کنم: اولین برخورد هر برنامهنویسی Ctrl+Shift+I و باز کردن inspect element هست که متوجه شدم در مثال ویکیپدیا کار نمیکند. (حیف!)
بعد از آن، به مشابه هر آدم عاقلی، شروع به جستجو و سرچ کردن میکنم و در سایت مشهور StackOverflow به این سوال برخوردم:
(چرا لینکها در ویرگول کار نمیکنند؟ نمیتوانم به چیزی لینک بدهم. باید مدیا بگذارم!)
پس سراسیمه به دنبال API ویکیپدیا و خواندن مستندات آن بودم که در سایت wikimedia (بنیادی که ویکیپدیا جزء آن است) به چنین مطلبی برخوردم:
البته، درست هست که این صفحه، به خودی خود، اطلاعات زیادی ندارد، اما لینکهای لازم به مستندات را ارائه میدهد.
به هر حال، من اینجا خلاصهاش میکنم تا نیاز نباشد که بروید تا کار با آن را ۰ تا ۱۰۰ یاد بگیرید.
برای آنکه API ویکیپدیا به شما جواب دهد، باید یک http request به صورتی که نشان داده شود، به آدرس زیر ارسال کنید (اگر نمیدانید http request چیست، فرض کنید که باید این آدرس را در URL bar مرورگر وارد کنید):
xx.wikipedia.org/w/api.php?something=some&sth=somth
البته، در URL بالا، بهجای xx باید کد زبان مطابق استاندارد ISO 639 macrolanguage گذاشته شود که برای انگلیسی، en و برای فارسی، fa هست.
و همچنین بعد از علامت سوال، بهجای something و some و ... باید مطابق دستور العمل ویکیپدیا، کلیدواژههای مناسب بگذریم.
در مثال من، برای گرفتن دادههای یک صفحه ویکیپدیا انگلیسی، دستور مناسب به مانند زیر هست:
https://en.wikipedia.org/w/api.php?action=parse&format=json&page=Iran
همانطور که میبینید، من با استفاده از page=Iran به API ویکیپدیا دستور دادهام تا صفحهای به نام Iran را از ویکیپدیا انگلیسی parse کند. شما اگر پارامتر page را برابر با هرچیزی غیر Iran بگذارید، دادههای آن صفحه مشخص شده مورد پردازش قرار میگیرند.
شایان ذکر هست که به بزرگ و کوچک بودن حروف حساس هست. پس دقت کنید!
همچنین در این API، به جای فاصله از _ استفاده میشود. مثلا صفحه Hello World به صورت hello_world درمیآید.
حالا من چطوری دستور دادم که parse کند؟ با استفاده از action=parse شما میتواند بهجای parse مقادیر دیگری مانند query,delete,... نیز بگذارید.
و در نهایت، با استفاده از format=json من گفتم که اطلاعات را به صورت json ارائه بده. میتوانستم بهجای json مقادیر xml, text, php,... را قرار دهم.
همچنین دقت کنید که page و action و format و بقیه پارامترها - اگر موجود بودند - توسط & از همدیگر جدا شدند.
* کسانی که با طراحی وب آشنایی دارند، این فرمت آنها را یاد GET method میاندازد.
مطمئنا اینکه به صورت دستی، url را وارد کنیم، خستهکننده هست. برای همین با استفاده از پایتون - بدون استفاده از هیچ کتابخانه خارجی - و با built-in moduleای به نام urllib اینکار را میکنیم:
import urllib.request
LANG = 'en'
PAGE_NAME = 'iran'
URL = "https://%s.wikipedia.org/w/api.php?action=parse&format=json&page=%s" % (LANG, PAGE_NAME)
byte_data = urllib.request.urlopen(URL)
خلاصه کد بالا این هست که ما آن urlای که میخواستیم را در متغییر URL ذخیره کردیم. چرا اینقدر غیرمستقیم و با دو متغییر دیگر؟ برای اینکه اگر خواستیم چیزی را تغییر دهیم، در دردسر نیفتیم و بهجای تغییر کامل url متغییرهای دیگر را تغییر بدهیم.
در خط آخر هم ما دادههای jsonای را در متغییر byte_data میریزیم زیرا آنها هنوز به فرمت JSON نیستند بلکه از نوع byte هستند.
کارکرد تابع urllib.request.urlopen این هست که اگر یک http یا https به آن بدهیم (مثل الان) به ما یک کلاس http.client.HTTPResponse برمیگرداند.
اطلاعات بیشتر در این باره را میتوانید در این دو لینک - که هر دو به سایت پایتون هستند - ببینید:
شاید برای خواننده این سوال پیش بیاید که چرا JSON و نه مثلا XML را انتخاب کردهام. دلیل این کار، سادگی پردازش این نوع فایل هست. اینگونه، بار پردازش خود json از سر ما کم میشود و فقط باید دادههای ویکیپدیا را پردازش کنیم.
import urllib.request
import json
LANG = 'en'
PAGE_NAME = 'Iran'
URL = "https://%s.wikipedia.org/w/api.php?action=parse&format=json&page=%s" % >
byte_data = urllib.request.urlopen(URL).read()
json_data = json.loads (byte_data)
text = json_data['parse']['text']['*']
اگر دقت کنید متوجه اضافه شدن import json برای پردازش فایلهای JSON میشوید.
همچنین به آخر تابعی که byte_data داده میشد، یک read() اضافه کردیم. افزدون .read() یعنی، جواب اجرای تابع read بر روی آن instance خاص و مشخص از کلاسی که توسط urllib.request.urlopen برمیگردد.
برای سادهتر شدن موضوع، فرض کنید ما تابع func1 را صدا میزنیم و این تابع به عنوان خروجی، یک کلاس جدید به ما میدهد؛ اما ما میخواهیم که نتیجه فراخوانی یکی از methodهای آن کلاس را داشته باشیم. راهحل طولانی این هست:
class Ex: def a(self): print("sth") def func1(): return Ex() Ex_instance = func1() Ex_instance.a()
اما راهحل سادهتر که بهجای آنکه متغییر جدیدی تعریف کند، بدون اختصاص دادن نام یک متغییر به یک instance از یک کلاس، فقط تابعی را روی یکی از instanceهای آن کلاس فرامیخواند:
class Ex: def a(self): print("sth") def func1(): return Ex() func1().a()
و این از این
در ادامه به تابع json.loads برمیخوریم که دادههایی از نوع byte (در مثال ما) یا str را به json تبدیل میکند. ما نتیجه این عملیات را (فایل json را) در متغییر json_data نگه داشتیم.
در بخش
json_data['parse']['text']['*']
این فرمت، به دلیل فرمت API وییپدیا هست. از آنجایی که این API دادههای زیادی مانند عنوان و غیره را نیز میفرستد، اما ما فقط به متن مطلب نیازمندیم، با این آدرس json فقط متن مطلب را در متغییر text ذخیره میکنیم. فایل json باید به صورت زیر باشد.
parse : {
text: { *: "The real text of the article "}
}
پردازش متغییر text باشد برای قسمت دوم
این مقاله قرار است قسمتهای دیگری نیز داشته باشد که احتمالا با اختلاف زمانی نسبتا زیادی نوشته شوند:
آمارگیری با ویکیپدیا (۲): پردازش html خام صفحه ویکیپدیا و استخراج داده
آمارگیری با ویکیپدیا (۳): آمارگیری و رسم نمودارها