Ամբողջ Habr-ը մեկ տվյալների բազայում

Բարի օր. Գրվելուց անցել է 2 տարի։ վերջին հոդվածը Հաբրի վերլուծության մասին, և որոշ կետեր փոխվել են:

Երբ ես ուզում էի ունենալ Habr-ի պատճենը, որոշեցի գրել վերլուծիչ, որը կպահի հեղինակների ամբողջ բովանդակությունը տվյալների բազայում: Ինչպես դա տեղի ունեցավ և ինչ սխալների հանդիպեցի, կարող եք կարդալ կտրվածքի տակ:

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)

Ամեն ինչ դասական է. մենք օգտագործում ենք Գեղեցիկ ապուր, հարցումներ և պատրաստ է արագ նախատիպը: Դա պարզապես…

  • Էջի ներբեռնումը մեկ թեմայում է

  • Եթե ​​դուք ընդհատեք սցենարի կատարումը, ապա ամբողջ տվյալների բազան ոչ մի տեղ չի գնա: Ի վերջո, commit-ը կատարվում է միայն ամբողջ վերլուծությունից հետո:
    Իհարկե, դուք կարող եք փոփոխություններ կատարել տվյալների բազայում յուրաքանչյուր տեղադրումից հետո, բայց հետո սցենարի կատարման ժամանակը զգալիորեն կաճի:

  • Առաջին 100 հոդվածների վերլուծությունն ինձ խլեց 000 ժամ:

Հաջորդը գտնում եմ օգտատիրոջ հոդվածը կոինտեգրված, որը ես կարդացի և գտա մի քանի կյանքի հաքեր՝ այս գործընթացը արագացնելու համար.

  • Multithreading-ի օգտագործումը երբեմն արագացնում է ներբեռնումը:
  • Դուք կարող եք ստանալ ոչ թե habr-ի ամբողջական տարբերակը, այլ դրա բջջային տարբերակը։
    Օրինակ, եթե աշխատասեղանի տարբերակում համակցված հոդվածը կշռում է 378 ԿԲ, ապա բջջային տարբերակում այն ​​արդեն 126 ԿԲ է։

Երկրորդ տարբերակը. Շատ թելեր, ժամանակավոր արգելք Հաբրից

Երբ ես համացանցում զննեցի python-ում բազմաթելային թեմայի շուրջ, ընտրեցի multiprocessing.dummy-ի ամենապարզ տարբերակը, նկատեցի, որ բազմաթելերի հետ միասին խնդիրներ են առաջանում:

SQLite3-ը չի ցանկանում աշխատել մեկից ավելի թելերի հետ.
ամրագրված check_same_thread=False, բայց այս սխալը միակը չէ, երբ փորձում եմ շտեմարան ներդնել, երբեմն սխալներ են առաջանում, որոնք ես չէի կարողանում լուծել։

Հետևաբար, ես որոշում եմ հրաժարվել հոդվածների ակնթարթային տեղադրումից անմիջապես տվյալների բազայում և, հիշելով համակցված լուծումը, որոշում եմ օգտագործել ֆայլեր, քանի որ ֆայլում բազմաթելային գրելու հետ կապված խնդիրներ չկան:

Habr-ը սկսում է արգելել երեքից ավելի թեմա օգտագործելու համար.
Հաբր անցնելու հատկապես եռանդուն փորձերը կարող են ավարտվել մի քանի ժամով 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)

Երրորդ տարբերակ. Վերջնական

Երկրորդ տարբերակի վրիպազերծման ժամանակ ես հայտնաբերեցի, որ 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

Ամբողջ Habr-ը մեկ տվյալների բազայում

Ես չթափեցի յուրաքանչյուր հոդվածի ամբողջական json-ը, այլ պահեցի միայն ինձ անհրաժեշտ դաշտերը.

  • id
  • is_tutorial
  • time_published
  • վերնագիր
  • պարունակություն
  • comments_count
  • lang-ը այն լեզուն է, որով գրված է հոդվածը: Առայժմ ունի միայն en և ru:
  • tags_string - գրառման բոլոր պիտակները
  • reading_count
  • հեղինակ
  • միավոր — հոդվածի վարկանիշ։

Այսպիսով, օգտագործելով 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 հոդված։ Պարզվում է, որ Habré-ի հոդվածների կեսից ավելին (228) թաքցվել կամ ջնջվել է։
  • Ամբողջ տվյալների բազան, որը բաղկացած է գրեթե կես միլիոն հոդվածներից, կշռում է 2.95 ԳԲ։ Սեղմված ձեւով - 495 ՄԲ:
  • Ընդհանուր առմամբ, Habré-ի հեղինակ է 37804 մարդ։ Հիշեցնում եմ, որ այս վիճակագրությունը միայն ուղիղ եթերից է։
  • Habré-ի ամենաարդյունավետ հեղինակը. ալիզար - 8774 հոդված:
  • Ամենաբարձր վարկանիշ ունեցող հոդվածը — 1448 պլյուս
  • Ամենաընթերցված հոդվածը — 1660841 դիտում
  • Ամենաշատ քննարկված հոդվածը — 2444 մեկնաբանություն

Դե, գագաթների տեսքովԼավագույն 15 հեղինակներԱմբողջ Habr-ը մեկ տվյալների բազայում
Լավագույն 15-ն ըստ վարկանիշիԱմբողջ Habr-ը մեկ տվյալների բազայում
Լավագույն 15 ընթերցվածԱմբողջ Habr-ը մեկ տվյալների բազայում
Քննարկված լավագույն 15-ըԱմբողջ Habr-ը մեկ տվյալների բազայում

Source: www.habr.com

Добавить комментарий