Бүх Habr нэг мэдээллийн санд

Өдрийн мэнд. Бичигдээд 2 жил болж байна. сүүлийн нийтлэл Хабрыг задлан шинжлэх тухай, зарим зүйл өөрчлөгдсөн.

Би Habr-ийн хуулбартай болохыг хүссэн үедээ зохиогчдын бүх агуулгыг мэдээллийн санд хадгалах задлан шинжлэгч бичихээр шийдсэн. Энэ нь хэрхэн тохиолдсон, ямар алдаатай тулгарсан бэ - та зүсэлтийн доор уншиж болно.

TLDR- мэдээллийн сангийн холбоос

Шинжилгээний анхны хувилбар. Нэг утас, олон асуудал

Эхлэхийн тулд би скриптийн прототип хийхээр шийдсэн бөгөөд нийтлэлийг татаж авсны дараа шууд задлан шинжилж, мэдээллийн санд байршуулах болно. Хоёр удаа бодолгүйгээр би sqlite3 ашигласан, учир нь. Энэ нь хөдөлмөр бага зарцуулсан: дотоод сервертэй байх шаардлагагүй, үүсгэсэн-харагдсан-устгагдсан гэх мэт.

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)

Бүх зүйл сонгодог - бид Үзэсгэлэнт шөл, хүсэлтийг ашигладаг бөгөөд хурдан прототип бэлэн болсон. Энэ зүгээр л…

  • Хуудас татаж авах нь нэг хэлхээнд байна

  • Хэрэв та скриптийн гүйцэтгэлийг тасалдуулбал мэдээллийн сан бүхэлдээ хаашаа ч гарахгүй. Эцсийн эцэст, даалгавар нь бүх задлан шинжилсний дараа л хийгддэг.
    Мэдээжийн хэрэг, та оруулах бүрийн дараа мэдээллийн санд өөрчлөлт оруулах боломжтой боловч дараа нь скриптийг гүйцэтгэх хугацаа мэдэгдэхүйц нэмэгдэх болно.

  • Эхний 100 нийтлэлийг задлан шинжлэхэд надад 000 цаг зарцуулсан.

Дараа нь би хэрэглэгчийн нийтлэлийг олдог хамтарсан, би уншаад энэ үйл явцыг хурдасгах хэд хэдэн лайф хакеруудыг олж мэдсэн:

  • Multithreading ашиглах нь заримдаа татан авалтыг хурдасгадаг.
  • Та habr-ийн бүрэн хувилбарыг биш, харин гар утасны хувилбарыг нь авах боломжтой.
    Жишээлбэл, ширээний хувилбарт нэгдмэл нийтлэл 378 КБ жинтэй бол гар утасны хувилбарт аль хэдийн 126 КБ жинтэй байна.

Хоёр дахь хувилбар. Олон утаснууд, Хабраас түр хугацаагаар хориг тавьсан

Би python хэл дээр multithreading сэдвээр интернетээр хайж байхдаа multiprocessing.dummy-ийн хамгийн энгийн хувилбарыг сонгоход олон урсгалтай холбоотой асуудлууд гарч ирснийг анзаарсан.

SQLite3 нь нэгээс олон утастай ажиллахыг хүсэхгүй байна.
тогтмол check_same_thread=False, гэхдээ энэ алдаа нь цорын ганц биш бөгөөд мэдээллийн санд оруулах гэж оролдох үед заримдаа би шийдэж чадахгүй алдаа гардаг.

Тиймээс би шууд мэдээллийн санд нийтлэл оруулахаас татгалзаж, коинтеграцтай шийдлийг санаж, файлд олон урсгалтай бичихэд асуудал гарахгүй тул файлуудыг ашиглахаар шийдсэн.

Хабр гурваас дээш утас ашиглахыг хориглож эхэлдэг.
Ялангуяа Хабр руу нэвтрэх гэсэн шаргуу оролдлого нь хэдэн цагийн турш IP хориг тавихад хүргэдэг. Тиймээс та зөвхөн 3 хэлхээ ашиглах хэрэгтэй, гэхдээ 100 гаруй өгүүллийг давтах хугацаа 26 секундээс 12 секунд болж багассан тул энэ нь аль хэдийн сайн байна.

Энэ хувилбар нь нэлээд тогтворгүй бөгөөд татан авалт нь олон тооны нийтлэлүүд дээр үе үе унадаг гэдгийг тэмдэглэх нь зүйтэй.

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)

Гурав дахь хувилбар. Финал

Хоёрдахь хувилбарыг дибаг хийж байхдаа Хабр гэнэт сайтын гар утасны хувилбарт ханддаг API-тай болохыг олж мэдсэн. Энэ нь зүгээр л json тул задлан шинжлэх шаардлагагүй тул гар утасны хувилбараас хурдан ачаалагддаг. Эцэст нь би зохиолоо дахин бичихээр шийдсэн.

Тиймээс олсон энэ холбоос API, та үүнийг задлан шинжилж эхлэх боломжтой.

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)

Энэ нь нийтлэл өөрөө болон үүнийг бичсэн зохиогчтой холбоотой талбаруудыг агуулдаг.

API.png

Бүх Habr нэг мэдээллийн санд

Би нийтлэл бүрийн бүтэн json-г орхиогүй, зөвхөн шаардлагатай талбаруудыг хадгалсан:

  • id
  • хичээл юм
  • нийтлэгдсэн_цаг
  • нэр
  • агуулга
  • сэтгэгдлийн_тоо
  • lang гэдэг нь нийтлэлийг бичсэн хэл юм. Одоогоор энэ нь зөвхөн en болон ru байна.
  • tags_string - нийтлэл дээрх бүх шошгууд
  • унших_тоо
  • Зохиогчийн
  • оноо - нийтлэлийн үнэлгээ.

Тиймээс, API ашиглан би скриптийг гүйцэтгэх хугацааг 8 url тутамд 100 секунд болгон бууруулсан.

Бидэнд шаардлагатай өгөгдлийг татаж авсны дараа бид үүнийг боловсруулж мэдээллийн санд оруулах хэрэгтэй. Надад ч гэсэн энэ асуудал гараагүй:

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)

Статистик

Уламжлал ёсоор та өгөгдлөөс зарим статистикийг гаргаж авах боломжтой.

  • Хүлээгдэж буй 490 татан авалтаас ердөө 406 нийтлэл татагдсан байна. Хабрегийн талаархи нийтлэлүүдийн талаас илүү хувь нь (228) нуугдаж эсвэл устгагдсан байна.
  • Бараг хагас сая нийтлэлээс бүрдсэн мэдээллийн сан бүхэлдээ 2.95 ГБ жинтэй. Шахсан хэлбэрээр - 495 MB.
  • Нийтдээ 37804 хүн Хабрегийн зохиогчид юм. Эдгээр статистик нь зөвхөн шууд бичлэгүүдээс авсан гэдгийг би танд сануулж байна.
  • Хабрегийн хамгийн үр бүтээлтэй зохиолч - Ализар - 8774 нийтлэл.
  • Хамгийн өндөр үнэлгээтэй нийтлэл - 1448 давуу тал
  • Хамгийн их уншсан нийтлэл — 1660841 удаа үзсэн
  • Хамгийн их хэлэлцсэн нийтлэл - 2444 сэтгэгдэл

За, оройн хэлбэрээрШилдэг 15 зохиолчБүх Habr нэг мэдээллийн санд
Үнэлгээгээр шилдэг 15Бүх Habr нэг мэдээллийн санд
Шилдэг 15 уншсанБүх Habr нэг мэдээллийн санд
Шилдэг 15-ыг хэлэлцсэнБүх Habr нэг мэдээллийн санд

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх