Bütün Habr bir verilənlər bazasında

Günortanız Xeyir. Yazılandan 2 il keçdi. son məqalə Habrın təhlili haqqında və bəzi məqamlar dəyişdi.

Habrın bir nüsxəsinə sahib olmaq istəyəndə, müəlliflərin bütün məzmununu verilənlər bazasında saxlayacaq bir analizator yazmaq qərarına gəldim. Necə oldu və hansı səhvlərlə qarşılaşdım - kəsik altında oxuya bilərsiniz.

TLDR- verilənlər bazası bağlantısı

Parserin ilk versiyası. Bir mövzu, çox problem

Başlamaq üçün, məqalənin təhlil ediləcəyi və yükləndikdən dərhal sonra verilənlər bazasına yerləşdiriləcəyi bir skript prototipi hazırlamaq qərarına gəldim. İki dəfə düşünmədən sqlite3 istifadə etdim, çünki. daha az əmək tələb edirdi: yerli serverə ehtiyac yoxdur, yaradılmış-görünür-silinmiş və bu kimi şeylər.

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)

Hər şey klassikdir - biz Gözəl Şorbadan istifadə edirik, sorğular və sürətli prototip hazırdır. Bu sadəcə…

  • Səhifənin endirilməsi bir mövzudadır

  • Skriptin icrasını dayandırsanız, bütün verilənlər bazası heç bir yerə getməyəcək. Axı, öhdəlik yalnız bütün təhlildən sonra həyata keçirilir.
    Əlbəttə ki, hər bir daxil etdikdən sonra verilənlər bazasında dəyişikliklər edə bilərsiniz, lakin sonra skriptin icra müddəti əhəmiyyətli dərəcədə artacaq.

  • İlk 100 məqaləni təhlil etmək mənə 000 saat çəkdi.

Sonra istifadəçinin məqaləsini tapıram kointeqrasiya, oxuduğum və bu prosesi sürətləndirmək üçün bir neçə həyat hiyləsi tapdım:

  • Multithreading istifadəsi bəzən yükləməni sürətləndirir.
  • Siz habrın tam versiyasını deyil, mobil versiyasını əldə edə bilərsiniz.
    Məsələn, masaüstü versiyada kointeqrasiya edilmiş məqalənin çəkisi 378 KB, mobil versiyada isə artıq 126 KB-dır.

İkinci versiya. Bir çox mövzu, Habrdan müvəqqəti qadağa

İnternetdə python-da multithreading mövzusunu araşdırarkən multiprocessing.dummy ilə ən sadə variantı seçdim, çox iş parçacığı ilə yanaşı problemlərin də ortaya çıxdığını gördüm.

SQLite3 birdən çox mövzu ilə işləmək istəmir.
sabit check_same_thread=False, lakin bu səhv tək deyil, verilənlər bazasına daxil etmək istəyərkən bəzən həll edə bilmədiyim xətalar olur.

Buna görə də, məqalələrin birbaşa verilənlər bazasına dərhal daxil edilməsindən imtina etmək qərarına gəldim və kointeqrativ həlli xatırlayaraq, fayldan istifadə etmək qərarına gəldim, çünki fayla çox yivli yazı ilə bağlı heç bir problem yoxdur.

Habr üç mövzudan çox istifadə üçün qadağa başlayır.
Xüsusilə Habr-a keçmək üçün edilən qeyrətli cəhdlər bir neçə saatlıq ip qadağası ilə nəticələnə bilər. Beləliklə, siz yalnız 3 mövzudan istifadə etməlisiniz, lakin bu, artıq yaxşıdır, çünki 100-dən çox məqalənin təkrarlanması vaxtı 26 saniyədən 12 saniyəyə qədər azalır.

Qeyd etmək lazımdır ki, bu versiya olduqca qeyri-sabitdir və yükləmələr vaxtaşırı çox sayda məqaləyə düşür.

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)

Üçüncü versiya. Final

İkinci versiyanı sazlayarkən, birdən Habr-ın saytın mobil versiyasının daxil olduğu bir API olduğunu kəşf etdim. O, mobil versiyadan daha sürətli yüklənir, çünki o, sadəcə json-dur, onu təhlil etməyə belə ehtiyac yoxdur. Sonda ssenarimi yenidən yazmaq qərarına gəldim.

Deməli, tapıb Bu linki API, siz onu təhlil etməyə başlaya bilərsiniz.

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)

Burada həm məqalənin özü, həm də onu yazan müəlliflə əlaqəli sahələr var.

API.png

Bütün Habr bir verilənlər bazasında

Mən hər məqalənin tam jsonunu atmadım, ancaq mənə lazım olan sahələri saxladım:

  • id
  • dərslikdir
  • vaxt_nəşr olundu
  • adı
  • məzmun
  • şərhlərin_sayı
  • lang məqalənin yazıldığı dildir. İndiyə qədər yalnız en və ru var.
  • tags_string - yazıdakı bütün teqlər
  • oxu_say
  • müəllif
  • xal — məqalə reytinqi.

Beləliklə, API istifadə edərək, skriptin icra müddətini 8 url üçün 100 saniyəyə endirdim.

Bizə lazım olan məlumatları yüklədikdən sonra onu emal edib verilənlər bazasına daxil etməliyik. Bununla da heç bir problemim yox idi:

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)

statistika

Yaxşı, ənənəvi olaraq, nəhayət, məlumatlardan bəzi statistika çıxara bilərsiniz:

  • Gözlənilən 490 406 yükləmədən yalnız 228 512 məqalə endirilib. Məlum olub ki, Habré ilə bağlı məqalələrin yarıdan çoxu (261894) gizlədilib və ya silinib.
  • Demək olar ki, yarım milyon məqalədən ibarət bütün verilənlər bazası 2.95 GB ağırlığındadır. Sıxılmış formada - 495 MB.
  • Ümumilikdə 37804 nəfər Habrenin müəllifidir. Xatırladıram ki, bu statistika yalnız canlı yazılardandır.
  • Habré-də ən məhsuldar müəllif - alizar - 8774 məqalə.
  • Ən yüksək reytinqli məqalə - 1448 müsbət
  • Ən çox oxunan məqalə — 1660841 baxış
  • Ən çox müzakirə olunan məqalə — 2444 şərh

Yaxşı, zirvələr şəklindəƏn yaxşı 15 müəllifBütün Habr bir verilənlər bazasında
Reytinq üzrə ilk 15Bütün Habr bir verilənlər bazasında
Ən yaxşı 15 oxunuşBütün Habr bir verilənlər bazasında
Top 15 Müzakirə olunurBütün Habr bir verilənlər bazasında

Mənbə: www.habr.com

Добавить комментарий