سڀ حبر هڪ ڊيٽابيس ۾

منجهند جو سلام. ان کي لکي 2 سال گذري ويا آهن. آخري مضمون حبر کي پارس ڪرڻ بابت، ۽ ڪجهه نقطا تبديل ٿي ويا آهن.

جڏهن مون چاهيو ته حبر جي هڪ ڪاپي هجي، مون فيصلو ڪيو ته هڪ پارسر لکڻ جو جيڪو مصنفن جي سڀني مواد کي ڊيٽابيس ۾ محفوظ ڪري ڇڏيندو. اهو ڪيئن ٿيو ۽ مون کي ڪهڙيون غلطيون مليون - توهان هيٺ پڙهي سگهو ٿا.

TLDR- ڊيٽابيس لنڪ

parser جو پهريون نسخو. ھڪڙو سلسلو، ڪيترائي مسئلا

شروع ڪرڻ سان، مون فيصلو ڪيو ته هڪ اسڪرپٽ پروٽوٽائپ ٺاهيو جنهن ۾ آرٽيڪل کي پارس ڪيو ويندو ۽ ڊائون لوڊ ٿيڻ تي فوري طور تي ڊيٽابيس ۾ رکيل هوندو. ٻه ڀيرا سوچڻ کان سواء، مون استعمال ڪيو 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 ڪلاڪ لڳا.

اڳيون مون کي صارف جو مضمون ڳوليو گڏيل، جنهن کي مون پڙهيو ۽ هن عمل کي تيز ڪرڻ لاءِ ڪجهه لائف هيڪس ڏٺا:

  • ملٽي ٿريڊنگ استعمال ڪرڻ وقت تي ڊائون لوڊ ڪرڻ جي رفتار کي تيز ڪري ٿو.
  • توهان حاصل ڪري سگهو ٿا habr جو مڪمل نسخو نه، پر ان جو موبائل نسخو.
    مثال طور، جيڪڏهن ڊيسڪ ٽاپ ورزن ۾ هڪ گڏيل مضمون جو وزن 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 url.

اسان کي گهربل ڊيٽا ڊائون لوڊ ڪرڻ کان پوء، اسان کي ان کي پروسيس ڪرڻ ۽ ان کي ڊيٽابيس ۾ داخل ڪرڻ جي ضرورت آهي. مون کي هن سان ڪو به مسئلو نه هو:

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 آرٽيڪل ڊائون لوڊ ڪيا ويا. اهو ظاهر ٿئي ٿو ته اڌ کان وڌيڪ (228) Habré تي آرٽيڪل لڪايا ويا يا ختم ڪيا ويا.
  • سڄو ڊيٽابيس، لڳ ڀڳ اڌ ملين مضمونن تي مشتمل آهي، وزن 2.95 GB آهي. ٺهيل شڪل ۾ - 495 ايم بي.
  • مجموعي طور تي، 37804 ماڻهو Habré جا ليکڪ آهن. مان توهان کي ياد ڏياريان ٿو ته اهي انگ اکر صرف لائيو پوسٽن مان آهن.
  • Habré تي سڀ کان وڌيڪ تخليقي ليکڪ - عليزر - 8774 آرٽيڪل.
  • مٿي ڏنل مضمون - 1448 پلس
  • سڀ کان وڌيڪ پڙهيل مضمون – 1660841 ڏٺو ويو
  • سڀ کان وڌيڪ بحث ٿيل آرٽيڪل - 2444 تبصرا

خير، مٿي جي صورت ۾مٿيان 15 ليکڪسڀ حبر هڪ ڊيٽابيس ۾
درجه بندي جي لحاظ کان مٿي 15سڀ حبر هڪ ڊيٽابيس ۾
مٿيان 15 پڙهوسڀ حبر هڪ ڊيٽابيس ۾
مٿي 15 بحث ڪيو ويوسڀ حبر هڪ ڊيٽابيس ۾

جو ذريعو: www.habr.com

تبصرو شامل ڪريو