Gach Habr i mbunachar sonraí amháin

Tráthnóna maith. Tá sé 2 bhliain ó scríobh mé é alt deireanach faoi ​​pharsáil Habr, agus tá roinnt rudaí tar éis athrú.

Nuair a theastaigh uaim cóip de Habr a bheith agam, chinn mé parsálaí a scríobh a shábhálfadh ábhar na n-údar ar fad i mbunachar sonraí. Conas a tharla sé agus cad iad na hearráidí a tháinig mé - is féidir leat léamh faoin gearrtha.

TL; DR - nasc chuig an mbunachar sonraí

An chéad leagan den pharsálaí. Snáithe amháin, go leor fadhbanna

Ar dtús, chinn mé fréamhshamhail de script a dhéanamh ina ndéanfaí an t-alt a pharsáil agus a chur sa bhunachar sonraí díreach tar éis é a íoslódáil. Gan smaoineamh faoi dhó, d'úsáid mé sqlite3, mar ... ní raibh sé chomh dian ar shaothar: ní gá go mbeadh freastalaí áitiúil agat, cruthaigh, breathnaigh, scrios agus rudaí mar sin.

aon_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)

Tá gach rud de réir na clasaiceach - úsáidimid Anraith Álainn, iarratais agus tá an fhréamhshamhail tapa réidh. Sin díreach ...

  • Íoslódáil an leathanach i snáithe amháin

  • Má chuireann tú isteach ar fhorghníomhú na scripte, ní rachaidh an bunachar sonraí ar fad in áit ar bith. Tar éis an tsaoil, ní dhéantar an gealltanas a fhorghníomhú ach amháin tar éis an pharsáil ar fad.
    Ar ndóigh, is féidir leat athruithe a dhéanamh ar an mbunachar sonraí tar éis gach cuir isteach, ach ansin méadóidh an t-am forghníomhaithe script go suntasach.

  • Thóg sé 100 n-uaire an chloig dom an chéad 000 alt a pharsáil.

Ansin faighim alt an úsáideora comhtháite, a léigh mé agus a fuair mé roinnt hacks saoil chun an próiseas seo a bhrostú:

  • Trí úsáid a bhaint as multithreading luasann an íoslódáil go suntasach.
  • Ní féidir leat an leagan iomlán de Habr a fháil, ach a leagan soghluaiste.
    Mar shampla, má tá meáchan 378 KB ag alt comhtháite sa leagan deisce, ansin sa leagan soghluaiste tá sé 126 KB cheana féin.

Dara leagan. Snáitheanna go leor, toirmeasc sealadach ó Habr

Nuair a rinne mé sciúradh ar an Idirlíon ar an ábhar multithreading i python agus roghnaigh an rogha is simplí le multiprocessing.dummy, thug mé faoi deara go raibh an chuma fadhbanna chomh maith le multithreading.

Níl SQLite3 ag iarraidh oibriú le níos mó ná snáithe amháin.
Seasta check_same_thread=False, ach ní hé seo an earráid amháin; agus mé ag iarraidh a chur isteach sa bhunachar sonraí, uaireanta tagann earráidí chun cinn nach raibh mé in ann a réiteach.

Dá bhrí sin, cinneann mé cur isteach toirt na n-alt go díreach isteach sa bhunachar sonraí a thréigean agus, ag cuimhneamh ar an réiteach comhtháthaithe, cinnim comhaid a úsáid, ós rud é nach bhfuil aon fhadhbanna ann le scríobh il-snáithithe chuig comhad.

Tosaíonn Habr toirmeasc as níos mó ná trí snáitheanna a úsáid.
D’fhéadfadh toirmeasc IP cúpla uair an chloig a bheith mar thoradh ar iarrachtaí díograiseacha go háirithe Habr a bhaint amach. Mar sin ní mór duit ach 3 snáithe a úsáid, ach tá sé seo go maith cheana féin, ós rud é go laghdaítear an t-am chun 100 alt a shórtáil ó 26 go 12 soicind.

Is fiú a thabhairt faoi deara go bhfuil an leagan seo go leor éagobhsaí, agus go mainneoidh an íoslódáil go tréimhsiúil ar líon mór alt.

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)

Tríú leagan. Deiridh

Agus an dara leagan á dhífhabhtú agam, fuair mé amach go bhfuil API ag Habr go tobann a bhfuil rochtain ag leagan soghluaiste an tsuímh air. Luchtaíonn sé níos tapúla ná an leagan soghluaiste, ós rud é nach bhfuil ann ach json, nach gá fiú a pharsáil. Sa deireadh, chinn mé mo script a athscríobh arís.

Mar sin, tar éis a fháil amach an nasc seo API, is féidir leat é a pharsáil.

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)

Tá réimsí ann a bhaineann leis an alt féin agus leis an údar a scríobh é.

API.png

Gach Habr i mbunachar sonraí amháin

Níor dhumpáil mé json iomlán gach ailt, ach níor shábháil mé ach na réimsí a bhí de dhíth orm:

  • id
  • is_teagasc
  • am_foilsithe
  • teideal
  • ábhar
  • tuairimí_comhaireamh
  • lang an teanga ina bhfuil an t-alt scríofa. Go dtí seo níl ann ach en agus ru.
  • tags_string — gach clib ón bpost
  • léamh_áireamh
  • údar
  • scór — rátáil alt.

Mar sin, ag baint úsáide as an API, laghdaigh mé an t-am forghníomhaithe script go 8 soicind in aghaidh an 100 url.

Tar éis dúinn na sonraí a theastaíonn uainn a íoslódáil, ní mór dúinn iad a phróiseáil agus a chur isteach sa bhunachar sonraí. Ní raibh aon fadhbanna leis seo ach an oiread:

parsálaí.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)

Staitisticí

Bhuel, go traidisiúnta, ar deireadh, is féidir leat roinnt staitisticí a bhaint as na sonraí:

  • As na 490 a rabhthas ag súil leo, níor íoslódáladh ach 406 alt. Tharlaíonn sé go raibh níos mó ná leath (228) de na hailt ar Habré folaithe nó scriosta.
  • Tá meáchan 2.95 GB sa bhunachar sonraí iomlán, ina bhfuil beagnach leath mhilliún alt. I bhfoirm chomhbhrúite - 495 MB.
  • San iomlán, tá 37804 údar ar Habré. Cuir i gcuimhne duit nach staitisticí iad seo ach ó phoist bheo.
  • An t-údar is táirgiúla ar Habré - alizar — 8774 alt.
  • Alt den scoth — 1448 móide
  • Is mó alt a léann — 1660841 radharc
  • Labhair an chuid is mó faoi alt — 2444 trácht

Bhuel, i bhfoirm bairrNa 15 údair is fearrGach Habr i mbunachar sonraí amháin
Barr 15 de réir rátálaGach Habr i mbunachar sonraí amháin
Barr 15 léamhGach Habr i mbunachar sonraí amháin
Barr 15 a pléadhGach Habr i mbunachar sonraí amháin

Foinse: will.com

Add a comment