تمام حبر ایک ڈیٹا بیس میں

صبح بخیر اسے لکھے ہوئے 2 سال ہوچکے ہیں۔ آخری مضمون حبر کو پارس کرنے کے بارے میں، اور کچھ نکات بدل گئے ہیں۔

جب میں نے حبر کی ایک کاپی حاصل کرنا چاہی تو میں نے ایک تجزیہ نگار لکھنے کا فیصلہ کیا جو مصنفین کے تمام مواد کو ڈیٹا بیس میں محفوظ کر دے گا۔ یہ کیسے ہوا اور مجھے کون سی غلطیوں کا سامنا کرنا پڑا - آپ کٹ کے نیچے پڑھ سکتے ہیں۔

TL؛ DR - ڈیٹا بیس لنک

تجزیہ کار کا پہلا ورژن۔ ایک دھاگہ، بہت سے مسائل

شروع کرنے کے لیے، میں نے ایک اسکرپٹ پروٹو ٹائپ بنانے کا فیصلہ کیا، جس میں مضمون کو ڈاؤن لوڈ کرنے اور ڈیٹا بیس میں رکھنے کے فوراً بعد پارس کیا جائے گا۔ دو بار سوچے بغیر، میں نے sqlite3 استعمال کیا، کیونکہ۔ یہ کم محنت والا تھا: مقامی سرور رکھنے کی ضرورت نہیں، تخلیق شدہ نظر آنے والا حذف شدہ اور اس طرح کی چیزیں۔

one_thread.py

from bs4 import BeautifulSoup
import sqlite3
import requests
from datetime import datetime

def main(min, max):
    conn = sqlite3.connect('habr.db')
    c = conn.cursor()
    c.execute('PRAGMA encoding = "UTF-8"')
    c.execute("CREATE TABLE IF NOT EXISTS habr(id INT, author VARCHAR(255), title VARCHAR(255), content  TEXT, tags TEXT)")

    start_time = datetime.now()
    c.execute("begin")
    for i in range(min, max):
        url = "https://m.habr.com/post/{}".format(i)
        try:
            r = requests.get(url)
        except:
            with open("req_errors.txt") as file:
                file.write(i)
            continue
        if(r.status_code != 200):
            print("{} - {}".format(i, r.status_code))
            continue

        html_doc = r.text
        soup = BeautifulSoup(html_doc, 'html.parser')

        try:
            author = soup.find(class_="tm-user-info__username").get_text()
            content = soup.find(id="post-content-body")
            content = str(content)
            title = soup.find(class_="tm-article-title__text").get_text()
            tags = soup.find(class_="tm-article__tags").get_text()
            tags = tags[5:]
        except:
            author,title,tags = "Error", "Error {}".format(r.status_code), "Error"
            content = "При парсинге этой странице произошла ошибка."

        c.execute('INSERT INTO habr VALUES (?, ?, ?, ?, ?)', (i, author, title, content, tags))
        print(i)
    c.execute("commit")
    print(datetime.now() - start_time)

main(1, 490406)

سب کچھ کلاسک ہے - ہم خوبصورت سوپ استعمال کرتے ہیں، درخواستیں اور ایک فوری پروٹو ٹائپ تیار ہے۔ بس یہی…

  • صفحہ ڈاؤن لوڈ ایک تھریڈ میں ہے۔

  • اگر آپ اسکرپٹ کے عمل میں رکاوٹ ڈالتے ہیں، تو پورا ڈیٹا بیس کہیں نہیں جائے گا۔ سب کے بعد، کمٹ صرف تمام تجزیہ کے بعد انجام دیا جاتا ہے.
    بلاشبہ، آپ ہر اندراج کے بعد ڈیٹا بیس میں تبدیلیاں کر سکتے ہیں، لیکن پھر اسکرپٹ پر عمل درآمد کا وقت نمایاں طور پر بڑھ جائے گا۔

  • پہلے 100 مضامین کو پارس کرنے میں مجھے 000 گھنٹے لگے۔

اگلا مجھے صارف کا مضمون ملا مربوط، جسے میں نے پڑھا اور اس عمل کو تیز کرنے کے لیے چند لائف ہیکس ملے۔

  • ملٹی تھریڈنگ کا استعمال بعض اوقات ڈاؤن لوڈنگ کو تیز کرتا ہے۔
  • آپ حبر کا مکمل ورژن نہیں بلکہ اس کا موبائل ورژن حاصل کر سکتے ہیں۔
    مثال کے طور پر، اگر ڈیسک ٹاپ ورژن میں ایک مربوط مضمون کا وزن 378 KB ہے، تو موبائل ورژن میں یہ پہلے سے ہی 126 KB ہے۔

دوسرا ورژن۔ بہت سے دھاگے، حبر کی طرف سے عارضی پابندی

جب میں نے python میں ملٹی تھریڈنگ کے موضوع پر انٹرنیٹ کو اسکور کیا تو میں نے multiprocessing.dummy کے ساتھ سب سے آسان آپشن کا انتخاب کیا، میں نے دیکھا کہ ملٹی تھریڈنگ کے ساتھ ساتھ مسائل بھی ظاہر ہوئے۔

SQLite3 ایک سے زیادہ تھریڈ کے ساتھ کام نہیں کرنا چاہتا.
طے شدہ check_same_thread=False، لیکن یہ غلطی صرف ایک نہیں ہے، جب ڈیٹا بیس میں داخل کرنے کی کوشش کی جاتی ہے، تو بعض اوقات ایسی غلطیاں پیدا ہوجاتی ہیں جنہیں میں حل نہیں کر پاتا۔

لہذا، میں نے ڈیٹا بیس میں براہ راست مضامین کے فوری اندراج کو ترک کرنے کا فیصلہ کیا اور مربوط حل کو یاد رکھتے ہوئے، میں فائلوں کو استعمال کرنے کا فیصلہ کرتا ہوں، کیونکہ فائل میں ملٹی تھریڈ لکھنے میں کوئی مسئلہ نہیں ہے۔

حبر تین سے زیادہ دھاگوں کے استعمال پر پابندی لگانا شروع کر دیتا ہے۔.
خاص طور پر حبر تک پہنچنے کی پرجوش کوششیں چند گھنٹوں کے لیے آئی پی پابندی کے ساتھ ختم ہو سکتی ہیں۔ اس لیے آپ کو صرف 3 تھریڈز استعمال کرنے ہوں گے، لیکن یہ پہلے سے ہی اچھا ہے، کیونکہ 100 سے زیادہ مضامین کو اعادہ کرنے کا وقت 26 سے کم کر کے 12 سیکنڈ کر دیا گیا ہے۔

یہ بات قابل غور ہے کہ یہ ورژن کافی غیر مستحکم ہے، اور ڈاؤن لوڈز وقتاً فوقتاً بڑی تعداد میں مضامین پر گرتے رہتے ہیں۔

async_v1.py

from bs4 import BeautifulSoup
import requests
import os, sys
import json
from multiprocessing.dummy import Pool as ThreadPool
from datetime import datetime
import logging

def worker(i):
    currentFile = "files\{}.json".format(i)

    if os.path.isfile(currentFile):
        logging.info("{} - File exists".format(i))
        return 1

    url = "https://m.habr.com/post/{}".format(i)

    try: r = requests.get(url)
    except:
        with open("req_errors.txt") as file:
            file.write(i)
        return 2

    # Запись заблокированных запросов на сервер
    if (r.status_code == 503):
        with open("Error503.txt", "a") as write_file:
            write_file.write(str(i) + "n")
            logging.warning('{} / 503 Error'.format(i))

    # Если поста не существует или он был скрыт
    if (r.status_code != 200):
        logging.info("{} / {} Code".format(i, r.status_code))
        return r.status_code

    html_doc = r.text
    soup = BeautifulSoup(html_doc, 'html5lib')

    try:
        author = soup.find(class_="tm-user-info__username").get_text()

        timestamp = soup.find(class_='tm-user-meta__date')
        timestamp = timestamp['title']

        content = soup.find(id="post-content-body")
        content = str(content)
        title = soup.find(class_="tm-article-title__text").get_text()
        tags = soup.find(class_="tm-article__tags").get_text()
        tags = tags[5:]

        # Метка, что пост является переводом или туториалом.
        tm_tag = soup.find(class_="tm-tags tm-tags_post").get_text()

        rating = soup.find(class_="tm-votes-score").get_text()
    except:
        author = title = tags = timestamp = tm_tag = rating = "Error" 
        content = "При парсинге этой странице произошла ошибка."
        logging.warning("Error parsing - {}".format(i))
        with open("Errors.txt", "a") as write_file:
            write_file.write(str(i) + "n")

    # Записываем статью в json
    try:
        article = [i, timestamp, author, title, content, tm_tag, rating, tags]
        with open(currentFile, "w") as write_file:
            json.dump(article, write_file)
    except:
        print(i)
        raise

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Необходимы параметры min и max. Использование: async_v1.py 1 100")
        sys.exit(1)
    min = int(sys.argv[1])
    max = int(sys.argv[2])

    # Если потоков >3
    # то хабр банит ipшник на время
    pool = ThreadPool(3)

    # Отсчет времени, запуск потоков
    start_time = datetime.now()
    results = pool.map(worker, range(min, max))

    # После закрытия всех потоков печатаем время
    pool.close()
    pool.join()
    print(datetime.now() - start_time)

تیسرا ورژن۔ فائنل

دوسرے ورژن کو ڈیبگ کرتے ہوئے، میں نے دریافت کیا کہ Habr، اچانک، ایک API ہے جس تک سائٹ کا موبائل ورژن رسائی حاصل کرتا ہے۔ یہ موبائل ورژن سے زیادہ تیزی سے لوڈ ہوتا ہے، کیونکہ یہ صرف json ہے، جسے پارس کرنے کی بھی ضرورت نہیں ہے۔ آخر میں، میں نے اپنی اسکرپٹ کو دوبارہ لکھنے کا فیصلہ کیا۔

تو، مل گیا اس لنک API، آپ اسے پارس کرنا شروع کر سکتے ہیں۔

async_v2.py

import requests
import os, sys
import json
from multiprocessing.dummy import Pool as ThreadPool
from datetime import datetime
import logging

def worker(i):
    currentFile = "files\{}.json".format(i)

    if os.path.isfile(currentFile):
        logging.info("{} - File exists".format(i))
        return 1

    url = "https://m.habr.com/kek/v1/articles/{}/?fl=ru%2Cen&hl=ru".format(i)

    try:
        r = requests.get(url)
        if r.status_code == 503:
            logging.critical("503 Error")
            return 503
    except:
        with open("req_errors.txt") as file:
            file.write(i)
        return 2

    data = json.loads(r.text)

    if data['success']:
        article = data['data']['article']

        id = article['id']
        is_tutorial = article['is_tutorial']
        time_published = article['time_published']
        comments_count = article['comments_count']
        lang = article['lang']
        tags_string = article['tags_string']
        title = article['title']
        content = article['text_html']
        reading_count = article['reading_count']
        author = article['author']['login']
        score = article['voting']['score']

        data = (id, is_tutorial, time_published, title, content, comments_count, lang, tags_string, reading_count, author, score)
        with open(currentFile, "w") as write_file:
            json.dump(data, write_file)

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Необходимы параметры min и max. Использование: asyc.py 1 100")
        sys.exit(1)
    min = int(sys.argv[1])
    max = int(sys.argv[2])

    # Если потоков >3
    # то хабр банит ipшник на время
    pool = ThreadPool(3)

    # Отсчет времени, запуск потоков
    start_time = datetime.now()
    results = pool.map(worker, range(min, max))

    # После закрытия всех потоков печатаем время
    pool.close()
    pool.join()
    print(datetime.now() - start_time)

اس میں خود مضمون اور مصنف سے متعلق فیلڈز شامل ہیں۔

API.png

تمام حبر ایک ڈیٹا بیس میں

میں نے ہر مضمون کے مکمل json کو ڈمپ نہیں کیا، لیکن صرف ان فیلڈز کو محفوظ کیا جن کی مجھے ضرورت تھی:

  • id
  • is_tutorial
  • time_published
  • عنوان
  • مواد
  • تبصرے_کاؤنٹ
  • lang وہ زبان ہے جس میں مضمون لکھا جاتا ہے۔ اب تک، اس میں صرف en اور ru ہے۔
  • tags_string - پوسٹ کے تمام ٹیگز
  • پڑھنے_کا شمار
  • مصنف
  • اسکور - مضمون کی درجہ بندی۔

اس طرح، API کا استعمال کرتے ہوئے، میں نے اسکرپٹ پر عمل درآمد کا وقت کم کر کے 8 سیکنڈ فی 100 یو آر ایل کر دیا۔

ہمیں مطلوبہ ڈیٹا ڈاؤن لوڈ کرنے کے بعد، ہمیں اس پر کارروائی کرنے اور اسے ڈیٹا بیس میں داخل کرنے کی ضرورت ہے۔ مجھے بھی اس کے ساتھ کوئی مسئلہ نہیں تھا:

parser.py

import json
import sqlite3
import logging
from datetime import datetime

def parser(min, max):
    conn = sqlite3.connect('habr.db')
    c = conn.cursor()
    c.execute('PRAGMA encoding = "UTF-8"')
    c.execute('PRAGMA synchronous = 0') # Отключаем подтверждение записи, так скорость увеличивается в разы.
    c.execute("CREATE TABLE IF NOT EXISTS articles(id INTEGER, time_published TEXT, author TEXT, title TEXT, content TEXT, 
    lang TEXT, comments_count INTEGER, reading_count INTEGER, score INTEGER, is_tutorial INTEGER, tags_string TEXT)")
    try:
        for i in range(min, max):
            try:
                filename = "files\{}.json".format(i)
                f = open(filename)
                data = json.load(f)

                (id, is_tutorial, time_published, title, content, comments_count, lang,
                 tags_string, reading_count, author, score) = data

                # Ради лучшей читаемости базы можно пренебречь читаемостью кода. Или нет?
                # Если вам так кажется, можно просто заменить кортеж аргументом data. Решать вам.

                c.execute('INSERT INTO articles VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', (id, time_published, author,
                                                                                        title, content, lang,
                                                                                        comments_count, reading_count,
                                                                                        score, is_tutorial,
                                                                                        tags_string))
                f.close()

            except IOError:
                logging.info('FileNotExists')
                continue

    finally:
        conn.commit()

start_time = datetime.now()
parser(490000, 490918)
print(datetime.now() - start_time)

اعداد و شمار

ٹھیک ہے، روایتی طور پر، آخر میں، آپ ڈیٹا سے کچھ اعدادوشمار نکال سکتے ہیں:

  • متوقع 490 ڈاؤن لوڈز میں سے صرف 406 مضامین ڈاؤن لوڈ کیے گئے۔ یہ پتہ چلتا ہے کہ Habré پر مضامین میں سے نصف سے زیادہ (228) چھپے یا حذف کردیئے گئے تھے۔
  • تقریباً نصف ملین مضامین پر مشتمل پورے ڈیٹا بیس کا وزن 2.95 جی بی ہے۔ کمپریسڈ شکل میں - 495 ایم بی۔
  • مجموعی طور پر 37804 لوگ Habré کے مصنف ہیں۔ میں آپ کو یاد دلاتا ہوں کہ یہ اعدادوشمار صرف لائیو پوسٹس سے ہیں۔
  • Habré پر سب سے زیادہ نتیجہ خیز مصنف - علیزر - 8774 مضامین۔
  • سب سے اوپر درجہ بند مضمون - 1448 پلس
  • سب سے زیادہ پڑھا ہوا مضمون — 1660841 ملاحظات
  • سب سے زیادہ زیر بحث مضمون - 2444 تبصرے

ٹھیک ہے، سب سے اوپر کی شکل میںسرفہرست 15 مصنفینتمام حبر ایک ڈیٹا بیس میں
درجہ بندی کے لحاظ سے ٹاپ 15تمام حبر ایک ڈیٹا بیس میں
ٹاپ 15 پڑھیںتمام حبر ایک ڈیٹا بیس میں
سرفہرست 15 زیر بحثتمام حبر ایک ڈیٹا بیس میں

ماخذ: www.habr.com

نیا تبصرہ شامل کریں