A h-uile Habr ann an aon stòr-dàta

Feasgar math. Tha 2 bhliadhna air a bhith ann bho chaidh a sgrìobhadh. artaigil mu dheireadh mu bhith a’ parsadh Habr, agus tha cuid de phuingean air atharrachadh.

Nuair a bha mi airson leth-bhreac de Habr a bhith agam, chuir mi romham parser a sgrìobhadh a shàbhaileadh susbaint nan ùghdaran gu lèir don stòr-dàta. Mar a thachair e agus dè na mearachdan a thachair mi - faodaidh tu leughadh fon ghearradh.

TLDR- ceangal stòr-dàta

A 'chiad dreach den parser. Aon snàithlean, mòran dhuilgheadasan

An toiseach, chuir mi romham prototype sgriobt a dhèanamh, anns am biodh an artaigil air a pharsadh sa bhad nuair a chaidh a luchdachadh sìos agus a chuir san stòr-dàta. Gun a bhith a 'smaoineachadh dà uair, chleachd mi sqlite3, oir. cha robh e cho dian air saothair: cha robh feum air frithealaiche ionadail, cruthaichte-sguabadh às agus stuth 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)

Tha a h-uile dad clasaigeach - bidh sinn a’ cleachdadh Beautiful Soup, iarrtasan agus tha prototype sgiobalta deiseil. Tha sin dìreach…

  • Tha luchdachadh sìos duilleag ann an aon snàithlean

  • Ma chuireas tu bacadh air coileanadh an sgriobt, cha tèid an stòr-dàta gu lèir gu àite sam bith. Às deidh na h-uile, chan eil an gealladh air a dhèanamh ach às deidh a h-uile parsadh.
    Gu dearbh, faodaidh tu atharrachaidhean a dhèanamh air an stòr-dàta às deidh gach cuir a-steach, ach an uairsin meudaichidh ùine cur an gnìomh an sgriobt gu mòr.

  • Thug parsadh a’ chiad 100 artaigil 000 uairean dhomh.

An uairsin lorg mi artaigil an neach-cleachdaidh co-aonaichte, a leugh mi agus a lorg mi beagan hacks beatha gus am pròiseas seo a luathachadh:

  • Bidh cleachdadh multithreading a’ luathachadh luchdachadh sìos aig amannan.
  • Chan fhaigh thu an dreach slàn den habr, ach an dreach gluasadach aige.
    Mar eisimpleir, ma tha cuideam 378 KB ann an artaigil co-aonaichte san dreach deasg, an uairsin anns an dreach gluasadach tha e mu thràth 126 KB.

An dàrna tionndadh. Mòran snàithlean, casg sealach bho Habr

Nuair a rinn mi sgùradh air an eadar-lìn air cuspair multithreading ann am python, thagh mi an roghainn as sìmplidh le multiprocessing.dummy, mhothaich mi gun do nochd duilgheadasan còmhla ri multithreading.

Chan eil SQLite3 airson a bhith ag obair le barrachd air aon snàithlean.
stèidhichte check_same_thread=False, ach chan e am mearachd seo an aon fhear, nuair a tha mi a’ feuchainn ri cuir a-steach don stòr-dàta, bidh mearachdan uaireannan a’ tachairt nach b’ urrainn dhomh fhuasgladh.

Mar sin, tha mi a 'co-dhùnadh a bhith a' trèigsinn cuir a-steach artaigilean gu dìreach a-steach don stòr-dàta agus, a 'cuimhneachadh air an fhuasgladh co-fhillte, tha mi a' co-dhùnadh na faidhlichean a chleachdadh, oir chan eil duilgheadasan ann le sgrìobhadh ioma-snàithlean gu faidhle.

Bidh Habr a 'tòiseachadh a' casg airson barrachd air trì snàithleanan a chleachdadh.
Faodaidh oidhirpean gu sònraichte dùrachdach faighinn troimhe gu Habr crìoch a chuir air casg ip airson dà uair a thìde. Mar sin feumaidh tu dìreach 3 snàithleanan a chleachdadh, ach tha seo math mar-thà, leis gu bheil an ùine airson ath-aithris còrr air 100 artaigil air a lughdachadh bho 26 gu 12 diogan.

Is fhiach a bhith mothachail gu bheil an dreach seo caran neo-sheasmhach, agus bidh luchdachadh sìos bho àm gu àm a ’tuiteam air àireamh mhòr de artaigilean.

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)

Treas dreach. Deireannach

Fhad ‘s a bha mi a’ dì-bhugachadh an dàrna dreach, fhuair mi a-mach gu bheil API aig Habr, gu h-obann, a gheibh an dreach gluasadach den làrach. Bidh e a’ luchdachadh nas luaithe na an dreach gluasadach, leis gur e dìreach json a th’ ann, nach eil eadhon feumach air parsadh. Aig a’ cheann thall, chuir mi romham mo sgriobt ath-sgrìobhadh a-rithist.

Mar sin, an dèidh lorg an ceangal seo API, faodaidh tu tòiseachadh air a pharsadh.

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)

Tha raointean ann co-cheangailte ris an artaigil fhèin agus ris an ùghdar a sgrìobh e.

API.png

A h-uile Habr ann an aon stòr-dàta

Cha do chuir mi sìos json slàn gach artaigil, ach cha do shàbhail mi ach na raointean a bha a dhìth orm:

  • id
  • is_tutorial
  • uair_foillsichte
  • tiotal a 'Chlàir
  • clàr na làraich
  • beachdan_cunntadh
  • lang an cànan anns a bheil an artaigil sgrìobhte. Gu ruige seo, chan eil ann ach en agus ru.
  • tags_string - a h-uile taga bhon phost
  • leughadh_cunnt
  • ùghdar
  • sgòr - rangachadh artaigil.

Mar sin, a’ cleachdadh an API, lughdaich mi ùine cur an gnìomh an sgriobt gu 8 diogan gach 100 url.

Às deidh dhuinn an dàta a tha a dhìth oirnn a luchdachadh sìos, feumaidh sinn a phròiseasadh agus a chuir a-steach don stòr-dàta. Cha robh duilgheadas sam bith agam le seo idir:

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)

Статистика

Uill, gu traidiseanta, mu dheireadh, faodaidh tu cuid de staitistig a tharraing bhon dàta:

  • De na 490 luchdachadh sìos ris an robh dùil, cha deach ach 406 artaigil a luchdachadh sìos. Tha e coltach gun deach còrr air leth (228) de na h-artaigilean air Habré fhalach no sguabadh às.
  • Tha an stòr-dàta gu lèir, anns a bheil faisg air leth mhillean artaigil, le cuideam 2.95 GB. Ann an cruth teann - 495 MB.
  • Gu h-iomlan, tha 37804 neach nan ùghdaran aig Habré. Tha mi gad chuimhneachadh nach eil na staitistig sin ach bho phuist beò.
  • An t-ùghdar as cinneasaiche air Habré - alizar — 8774 alt.
  • Artaigil aig an ìre as àirde — 1448 buannachdan
  • Artaigil a leugh a’ mhòr-chuid - 1660841 seallaidhean
  • Artaigil as motha air a bheilear a’ beachdachadh — 2444 beachd

Uill, ann an cruth mullaichNa 15 sgrìobhadairean as fheàrrA h-uile Habr ann an aon stòr-dàta
15 as àirde a rèir rangachadhA h-uile Habr ann an aon stòr-dàta
Leughadh 15 as fheàrrA h-uile Habr ann an aon stòr-dàta
Top 15 air a dheasbadA h-uile Habr ann an aon stòr-dàta

Source: www.habr.com

Cuir beachd ann