Ҳама 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)

Ҳама чиз классикӣ аст - мо шӯрбои зеборо истифода мебарем, дархостҳо ва прототипи зуд омода аст. Ин танҳо…

  • Зеркашии саҳифа дар як ришта аст

  • Агар шумо иҷрои скриптро қатъ кунед, он гоҳ тамоми базаи маълумот ба ҳеҷ куҷо намеравад. Баъд аз ҳама, супориш танҳо пас аз ҳама таҳлил иҷро карда мешавад.
    Албатта, шумо метавонед пас аз ҳар як воридкунӣ ба пойгоҳи додаҳо тағирот ворид кунед, аммо он гоҳ вақти иҷрои скрипт ба таври назаррас афзоиш хоҳад ёфт.

  • Таҳлили 100 000 мақолаи аввал ба ман 8 соат лозим шуд.

Минбаъд ман мақолаи корбарро пайдо мекунам коинтеграцияшуда, ки ман онро хондам ва барои суръат бахшидан ба ин раванд чанд лайфхак ёфтам:

  • Истифодаи мултипликатори боргирӣ баъзан суръатро метезонад.
  • Шумо на версияи пурраи хабр, балки версияи мобилии онро дастрас карда метавонед.
    Масалан, агар мақолаи коинтегратсияшуда дар версияи мизи корӣ 378 КБ вазн дошта бошад, пас дар версияи мобилӣ он аллакай 126 КБ аст.

Версияи дуюм. Бисёр риштаҳо, манъи муваққатӣ аз Habr

Вақте ки ман Интернетро дар мавзӯи multithreading дар 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)

Версияи сеюм. Финал

Ҳангоми ислоҳи версияи дуюм, ман фаҳмидам, ки Ҳабр ногаҳон 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
  • дарс_ аст
  • 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 512 мақола зеркашида шудааст. Маълум мешавад, ки зиёда аз нисфи (261894) мақолаҳо дар бораи Ҳабре пинҳон ё нест карда шудаанд.
  • Тамоми махзани маълумот, ки аз қариб ним миллион мақола иборат аст, 2.95 ГБ вазн дорад. Дар шакли фишурда - 495 MB.
  • Дар маҷмӯъ, 37804 нафар муаллифони Ҳабре мебошанд. Ба шумо хотиррасон мекунам, ки ин омор танҳо аз паёмҳои зинда аст.
  • Муаллифи пурмаҳсул дар Ҳабре - Ализар - 8774 мақола.
  • Мақолаи баландтарин — 1448 фоида
  • Мақолаи бештар хондашуда — 1660841 дида баромаданд
  • Мақолаи бештар баррасӣшуда — 2444 тафсир

Хуб, дар шакли болоБеҳтарин 15 муаллифҲама Habr дар як пойгоҳи додаҳо
Топ 15 аз рӯи рейтингҲама Habr дар як пойгоҳи додаҳо
Беҳтарин 15 хонданҲама Habr дар як пойгоҳи додаҳо
Top 15 МуҳокимаҲама Habr дар як пойгоҳи додаҳо

Манбаъ: will.com

Илова Эзоҳ