Hemî Habr di yek databasê de

Paş nîvro. Di ser nivîsandina min re 2 sal derbas bûn gotara dawî li ser parskirina Habrê, û hin tişt hatine guhertin.

Dema ku min xwest kopiyek Habrê hebe, min biryar da ku parsekek binivîsim ku dê hemî naveroka nivîskaran di databasek de tomar bike. Ew çawa çêbû û ez bi kîjan xeletiyan re rû bi rû bûm - hûn dikarin di bin qutbûnê de bixwînin.

TL;DR - girêdana databasê

Guhertoya yekem a parser. Yek mijar, gelek pirsgirêk

Ji bo destpêkê, min biryar da ku ez prototîpek skrîptê bikim ku tê de, tavilê piştî dakêşandinê, gotar were pars kirin û di databasê de were danîn. Bêyî ku du caran bifikirim, min sqlite3 bikar anî, ji ber ku ... ew kêmtir kedkar bû: hûn ne hewce ne ku serverek herêmî hebe, biafirînin, binihêrin, jêbirin û tiştên weha.

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)

Her tişt li gorî klasîkan e - em Şorbeya Bedew bikar tînin, daxwaz dikin û prototîpa bilez amade ye. Ew tenê ...

  • Rûpel di yek mijarê de tê daxistin

  • Ger hûn pêkanîna skrîptê qut bikin, wê hingê dê databasa tevahî neçe cîhek. Beriya her tiştî, commit tenê piştî hemî parskirinê tê darve kirin.
    Bê guman, hûn dikarin piştî her têketinê guheztinan li databasê bikin, lê dûv re dema darvekirina skrîptê dê pir zêde bibe.

  • Parskirina 100 gotarên pêşîn ji min re 000 saet girt.

Dûv re ez gotara bikarhêner dibînim hevgirtî, ya ku min xwend û gelek hakên jiyanê dîtin ku vê pêvajoyê bilezînin:

  • Bikaranîna multithreading dakêşanê bi girîngî bileztir dike.
  • Hûn dikarin ne guhertoya tevahî ya Habr, lê guhertoya wê ya mobîl bistînin.
    Mînakî, heke gotarek hevgirtî di guhertoya sermaseyê de 378 KB giraniya wê ye, wê hingê di guhertoya mobîl de ew jixwe 126 KB ye.

Guhertoya duyemîn. Gelek mijar, qedexeya demkî ji Habrê re

Dema ku min li ser Înternetê li ser mijara pirthreading di python de geriyam û bi multiprocessing.dummy vebijarka herî hêsan hilbijart, min dît ku pirsgirêk digel pirtirsandinê derketin holê.

SQLite3 naxwaze bi zêdetirî yek mijarê re bixebite.
Fixed check_same_thread=False, lê ev xeletî ne tenê ye; dema ku hewl didin ku têxin nav databasê, carinan xeletiyên ku min nekarî çareser bikim derdikevin.

Ji ber vê yekê, ez biryar didim ku dev ji danasîna tavilê ya gotaran rasterast di nav databasê de berdim û, bi bîranîna çareseriya hevgirtî, ez biryar didim ku pelan bikar bînim, ji ber ku di nivîsandina pir-mijara pelê de pirsgirêk tune.

Habr ji ber bikaranîna zêdetirî sê mijaran dest bi qedexekirinê dike.
Bi taybetî hewildanên bi xîret ku bigihîjin Habrê dibe ku ji bo çend demjimêran bibe sedema qedexekirina IP-yê. Ji ber vê yekê hûn neçar in ku tenê 3 mijaran bikar bînin, lê ev jixwe baş e, ji ber ku dema dabeşkirina 100 gotaran ji 26 ber 12 saniyeyan kêm dibe.

Hêjayî gotinê ye ku ev guhertoyek pir bêhêz e, û dakêşana periyodîk li ser hejmareke mezin ji gotaran têk diçe.

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)

Guhertoya sêyemîn. Dawî

Dema ku guhertoya duyemîn jêbirin, min kifş kir ku Habr ji nişkê ve xwedan API-yek e ku ji hêla guhertoya mobîl a malperê ve tê gihîştin. Ew ji guhertoya mobîl zûtir bar dike, ji ber ku ew tenê json e, ku ne hewce ye ku were pars kirin. Di dawiyê de, min biryar da ku ez senaryoya xwe ji nû ve binivîsim.

Ji ber vê yekê, kifş kirin vê girêdanê API, hûn dikarin dest bi parskirina wê bikin.

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)

Ew zeviyên ku hem bi gotara xwe û hem jî bi nivîskarê ku ew nivîsandiye ve girêdayî ne.

API.png

Hemî Habr di yek databasê de

Min json tam a her gotarê neavêt, lê tenê zeviyên ku min hewce ne hilanîn:

  • id
  • is_tutorial
  • time_published
  • nav
  • naveroka
  • comments_count
  • lang zimanê ku gotar pê tê nivîsandin e. Heta niha tenê en û ru dihewîne.
  • tags_string - hemî etîketên ji postê
  • xwendin_hejmar
  • nivîskar
  • xal - nirxandina gotarê.

Bi vî rengî, bi karanîna API-ê, min dema darvekirina nivîsê daxist 8 saniyeyan ji 100 url.

Piştî ku me daneyên ku em hewce ne dakêşin, divê em wan pêvajoyê bikin û têkevin databasê. Di vê yekê de jî pirsgirêk tune bûn:

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)

Статистика

Welê, bi kevneşopî, di dawiyê de, hûn dikarin hin statîstîkan ji daneyan derxînin:

  • Ji hêviya 490, tenê 406 gotar hatin dakêşandin. Derket holê ku ji nîvî zêdetir (228) gotarên li ser Habré hatine veşartin an jêbirin.
  • Tevahiya databasê, ku ji hema nîv mîlyon gotaran pêk tê, 2.95 GB giran e. Di forma pêçandî de - 495 MB.
  • Bi tevahî, li ser Habré 37804 nivîskar hene. Bila ez ji we re bînim bîra we ku ev statîstîk tenê ji peyamên zindî ne.
  • Nivîskarê herî berhemdar li ser Habré - alizar - 8774 gotar.
  • gotara herî bilind - 1448 zêde
  • Gotara herî xwendin - 1660841 dîtin
  • Li ser gotara herî zêde tê axaftin - 2444 şîrove

Baş e, di forma topsTop 15 nivîskarênHemî Habr di yek databasê de
Top 15 ji aliyê ratingHemî Habr di yek databasê de
Top 15 xwendinHemî Habr di yek databasê de
Top 15 Nîqaş kirinHemî Habr di yek databasê de

Source: www.habr.com

Add a comment