Бардык Habr бир маалымат базасында

Кутмандуу күнүң менен. Жазылганына 2 жыл болду. акыркы макала Хабрды талдоо жөнүндө жана кээ бир пункттар өзгөрдү.

Мен Хабрдын көчүрмөсүн алгым келгенде, авторлордун бардык мазмунун маалымат базасына сактай турган талдоочу жазууну чечтим. Бул кантип болду жана кандай каталарга туш болдум - сиз кесиптин астынан окуй аласыз.

TLDR- маалымат базасына шилтеме

Талдоочунун биринчи версиясы. Бир жип, көп көйгөйлөр

Баштоо үчүн, мен скрипттин прототибин жасоону чечтим, анда макала жүктөлүп алынгандан кийин дароо талданат жана маалымат базасына жайгаштырылат. Эки жолу ойлонбостон, мен 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)

Баары классикалык - биз Beautiful Soup колдонобуз, суроо-талаптар жана тез прототиби даяр. Бул жөн эле…

  • Баракты жүктөө бир темада

  • Эгерде сиз скрипттин аткарылышын үзгүлтүккө учуратсаңыз, анда бүт маалымат базасы эч кайда кетпейт. Анткени, милдеттенме бардык талдоодон кийин гана аткарылат.
    Албетте, ар бир киргизилгенден кийин маалымат базасына өзгөртүүлөрдү киргизе аласыз, бирок андан кийин скрипттин аткарылуу убактысы бир топ көбөйөт.

  • Алгачкы 100 000 макаланы талдоо мага 8 саатты алды.

Кийинки мен колдонуучунун макаласын табам коинтеграцияланган, мен аны окуп, бул процессти тездетүү үчүн бир нече лайфхактарды таптым:

  • Multithreading колдонуу кээде жүктөөнү тездетет.
  • Сиз хабрдын толук версиясын эмес, анын мобилдик версиясын ала аласыз.
    Мисалы, эгер десктоп версиясында коинтеграцияланган макаланын салмагы 378 КБ болсо, мобилдик версияда 126 КБ.

Экинчи версия. Көптөгөн темалар, Хабрдан убактылуу тыюу салуу

Мен интернетти pythonдо көп агым темасын изилдегенимде, мен multiprocessing.dummy менен эң жөнөкөй вариантты тандадым, көп агым менен катар көйгөйлөр пайда болгонун байкадым.

SQLite3 бирден ашык жип менен иштөөнү каалабайт.
белгиленген check_same_thread=False, бирок бул ката жалгыз эмес, маалымат базасына киргизүүгө аракет кылып жатканда, кээде мен чече албаган каталар пайда болот.

Ошондуктан, мен макалаларды түздөн-түз маалымат базасына заматта киргизүүдөн баш тартууну чечтим жана коинтеграцияланган чечимди эстеп, файлдарды колдонууну чечтим, анткени файлга көп жиптүү жазууда көйгөйлөр жок.

Хабр үчтөн ашык жипти колдонууга тыюу сала баштайт.
Айрыкча Хабрга өтүүгө болгон ынталуу аракеттер бир нече саатка IP тыюу салуу менен аякташы мүмкүн. Демек, сиз болгону 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)

Үчүнчү версия. Финал

Экинчи версиянын мүчүлүштүктөрүн оңдоодо мен Хабрда күтүлбөгөн жерден сайттын мобилдик версиясы кире турган 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

Бардык Habr бир маалымат базасында

Мен ар бир макаланын толук json файлын таштаган жокмун, бирок мага керектүү талааларды гана сактап койдум:

  • id
  • is_tutorial
  • time_published
  • наам
  • ыраазы
  • комментарийлердин_саны
  • lang - макала жазылган тил. Азырынча анын en жана ru гана бар.
  • tags_string - посттогу бардык тэгдер
  • окуу_саны
  • жазуучу
  • балл — макала рейтинги.

Ошентип, API колдонуп, мен скрипттин аткарылуу убактысын 8 url үчүн 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 жүктөөнүн ичинен 228 512 гана макала жүктөлгөн. Көрсө, Хабредеги макалалардын жарымынан көбү (261894) жашырылган же өчүрүлгөн.
  • Дээрлик жарым миллион макаладан турган бүт маалымат базасы 2.95 ГБ салмакты түзөт. Кысылган түрдө - 495 МБ.
  • Бардыгы болуп 37804 адам Хабренин авторлору. Бул статистика жандуу билдирүүлөрдөн гана экенин эскертем.
  • Habré боюнча эң жемиштүү жазуучу - ализар - 8774 макала.
  • Жогорку бааланган макала — 1448 плюс
  • Эң көп окуган макала — 1660841 көрүүлөр
  • Эң көп талкууланган макала — 2444 комментарий

Ооба, чокулар түрүндөМыкты 15 авторБардык Habr бир маалымат базасында
Рейтинг боюнча топ 15Бардык Habr бир маалымат базасында
Эң мыкты 15 окууБардык Habr бир маалымат базасында
Топ 15 талкууландыБардык Habr бир маалымат базасында

Source: www.habr.com

Комментарий кошуу