Habr rehetra ao anaty tahiry iray

Salama. 2 taona izay no nanoratana azy. lahatsoratra farany momba ny famakafakana an'i Habr, ary niova ny teboka sasany.

Rehefa te-hanana dika mitovy amin'ny Habr aho, dia nanapa-kevitra ny hanoratra parser izay hamonjy ny votoatin'ny mpanoratra rehetra ao amin'ny tahiry. Ahoana no nitrangan'izany ary inona ny hadisoana hitako - azonao vakiana eo ambanin'ny fanapahana.

TLDR- rohy database

Ny dikan-teny voalohany amin'ny parser. Loha iray, olana maro

Hanombohana dia nanapa-kevitra ny hanao prototype script aho izay handinihana ilay lahatsoratra ary hapetraka ao anaty angon-drakitra avy hatrany rehefa misintona. Tsy nieritreritra indroa aho dia nampiasa sqlite3, satria. tsy dia nanan-kery loatra izany: tsy mila manana mpizara eo an-toerana, noforonina-mijery-fafaina sy ny toy izany.

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)

Ny zava-drehetra dia mahazatra - mampiasa lasopy tsara tarehy izahay, ny fangatahana ary ny prototype haingana dia vonona. Izany ihany…

  • Ny fampidinana pejy dia ao anatin'ny kofehy iray

  • Raha manapaka ny fanatanterahana ny script ianao, dia tsy handeha na aiza na aiza ny tahiry manontolo. Rehefa dinihina tokoa, ny fanoloran-tena dia atao aorian'ny famafazana rehetra.
    Mazava ho azy, azonao atao ny manova ny angon-drakitra aorian'ny fampidirana tsirairay, fa avy eo dia hitombo be ny fotoana hanatanterahana ny script.

  • Naharitra adiny 100 ny famakiana ireo lahatsoratra 000 voalohany.

Manaraka izany dia hitako ny lahatsoratry ny mpampiasa cointegrated, izay novakiako ary nahita hacks vitsivitsy momba ny fiainana mba hanafainganana ity dingana ity:

  • Ny fampiasana multithreading dia manafaingana ny fampidinana indraindray.
  • Tsy ny habr feno no azonao azonao, fa ny dikan-teny finday.
    Ohatra, raha 378 KB ny lahatsoratra mitambatra ao amin'ny desktop version, dia efa 126 KB izany amin'ny version mobile.

Dika faharoa. Lohahevitra maro, fandrarana vonjimaika avy amin'i Habr

Rehefa nikaroka Internet momba ny lohahevitra momba ny multithreading amin'ny python aho, dia nisafidy ny safidy tsotra indrindra miaraka amin'ny multiprocessing.dummy, hitako fa misy olana miaraka amin'ny multithreading.

SQLite3 dia tsy te-hiasa amin'ny kofehy mihoatra ny iray.
raikitra check_same_thread=False, fa tsy io hadisoana io ihany, rehefa manandrana mampiditra ao amin'ny angon-drakitra dia misy hadisoana indraindray izay tsy vitako.

Noho izany, manapa-kevitra ny handao ny fampidirana lahatsoratra avy hatrany ao amin'ny tahiry aho ary, amin'ny fitadidiako ny vahaolana iraisana, dia manapa-kevitra ny hampiasa rakitra aho, satria tsy misy olana amin'ny fanoratana maromaro amin'ny rakitra iray.

Nanomboka nandrara ny fampiasana kofehy mihoatra ny telo i Habr.
Ny fiezahana mazoto indrindra mankany amin'ny Habr dia mety hiafara amin'ny fandraràna ip mandritra ny ora roa. Noho izany dia tsy maintsy mampiasa kofehy 3 fotsiny ianao, fa efa tsara izany, ka ny fotoana hamerenana lahatsoratra mihoatra ny 100 dia ahena amin'ny 26 ka hatramin'ny 12 segondra.

Tsara ny manamarika fa ity dikan-teny ity dia somary tsy miovaova, ary ny fampidinana tsindraindray dia mianjera amin'ny lahatsoratra marobe.

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)

Dika fahatelo. farany

Raha nanala ny dikan-teny faharoa aho, dia hitako fa i Habr, tampoka, dia manana API izay idiran'ny kinova finday amin'ny tranokala. Mandefa haingana kokoa noho ny dikan-teny finday izy io, satria json fotsiny izy io, izay tsy mila fehezina akory. Tamin'ny farany dia nanapa-kevitra ny hamerina hanoratra ny soratro indray aho.

Noho izany, rehefa nahita ity rohy ity API, azonao atao ny manomboka mamaky azy.

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)

Ahitana saha mifandraika amin'ny lahatsoratra sy ny mpanoratra nanoratra azy io.

API.png

Habr rehetra ao anaty tahiry iray

Tsy nariako ny json fenon'ny lahatsoratra tsirairay, fa ny saha nilaiko ihany no notahiriko:

  • id
  • dia_tutorial
  • time_published
  • lohateny
  • afa-po
  • comment_count
  • lang no fiteny nanoratana ny lahatsoratra. Hatreto dia en sy ru ihany no misy azy.
  • tags_string - marika rehetra avy amin'ny lahatsoratra
  • famakiana_isa
  • mpanoratra
  • score — naoty lahatsoratra.

Noho izany, tamin'ny fampiasana ny API dia nahenako ho 8 segondra isaky ny url 100 ny fotoana famonoana script.

Aorian'ny fampidinana ny angon-drakitra ilaintsika dia mila manamboatra izany isika ary ampidiro ao amin'ny tahiry. Tsy nanana olana tamin'ity koa aho:

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)

Статистика

Eny, amin'ny fomba mahazatra, amin'ny farany, azonao atao ny maka antontan'isa avy amin'ny angon-drakitra:

  • Amin'ireo fampidinana 490 noheverina, lahatsoratra 406 ihany no nalaina. Hita fa mihoatra ny antsasany (228) ny lahatsoratra momba ny Habré no nafenina na nofafana.
  • Ny angon-drakitra manontolo, ahitana lahatsoratra efa ho antsasaka tapitrisa, dia milanja 2.95 GB. Amin'ny endrika compressed - 495 MB.
  • Amin'ny fitambarany, olona 37804 no mpanoratra ny Habré. Mampahatsiahy anao aho fa ireo antontan'isa ireo dia avy amin'ny lahatsoratra mivantana ihany.
  • Ny mpanoratra mamokatra indrindra ao amin'ny Habré - alizar - 8774 lahatsoratra.
  • Lahatsoratra naoty ambony indrindra — 1448 miampy
  • lahatsoratra be mpamaky indrindra — 1660841 fijery
  • Lahatsoratra be resaka — 2444 fanehoan-kevitra

Eny ary, amin'ny endriky ny tamponyMpanoratra 15 ambonyHabr rehetra ao anaty tahiry iray
Top 15 amin'ny naotyHabr rehetra ao anaty tahiry iray
Top 15 mamakyHabr rehetra ao anaty tahiry iray
Top 15 noresahinaHabr rehetra ao anaty tahiry iray

Source: www.habr.com

Add a comment