كل هبر في قاعدة بيانات واحدة

مساء الخير. لقد مر عامان منذ كتابته. المقال الأخير حول تحليل هبر ، وقد تغيرت بعض النقاط.

عندما أردت الحصول على نسخة من Habr ، قررت أن أكتب محللًا يحفظ كل محتوى المؤلفين في قاعدة البيانات. كيف حدث ذلك وما الأخطاء التي واجهتها - يمكنك أن تقرأ تحت القص.

TL ؛ DR - ارتباط قاعدة البيانات

الإصدار الأول من المحلل اللغوي. خيط واحد ، مشاكل كثيرة

بادئ ذي بدء ، قررت إنشاء نموذج أولي للبرنامج النصي يتم فيه تحليل المقالة ووضعها في قاعدة البيانات فور التنزيل. دون التفكير مرتين ، استخدمت 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 كيلو بايت.

الإصدار الثاني. خيوط كثيرة ، منع مؤقت من هبر

عندما بحثت في الإنترنت حول موضوع تعدد مؤشرات الترابط في بيثون ، اخترت الخيار الأبسط مع المعالجة المتعددة الدمية ، لاحظت ظهور المشكلات جنبًا إلى جنب مع تعدد مؤشرات الترابط.

لا يريد SQLite3 العمل مع أكثر من مؤشر ترابط واحد.
مُثَبَّت check_same_thread=False، ولكن هذا الخطأ ليس الخطأ الوحيد ، عند محاولة الإدراج في قاعدة البيانات ، تحدث أحيانًا أخطاء لم أتمكن من حلها.

لذلك ، قررت التخلي عن الإدراج الفوري للمقالات مباشرة في قاعدة البيانات ، وتذكر الحل المدمج ، قررت استخدام الملفات ، لأنه لا توجد مشاكل في الكتابة متعددة الخيوط في ملف.

يبدأ هبر في الحظر لاستخدامه أكثر من ثلاثة خيوط.
يمكن أن تنتهي المحاولات المتحمسة للوصول إلى Habr بحظر 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)

النسخة الثالثة. أخير

أثناء تصحيح أخطاء الإصدار الثاني ، اكتشفت أن Habr ، فجأة ، لديه واجهة برمجة تطبيقات يمكن لنسخة الجوال من الموقع الوصول إليها. يتم تحميله بشكل أسرع من إصدار الهاتف المحمول ، لأنه مجرد 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

كل هبر في قاعدة بيانات واحدة

لم أتخلص من ملف json الكامل لكل مقال ، لكنني احتفظت فقط بالحقول التي أحتاجها:

  • id
  • is_tutorial
  • الوقت_النشر
  • لقب
  • محتوى
  • comments_count
  • lang هي اللغة التي كُتب بها المقال. حتى الآن ، لديها فقط en و ru.
  • tags_string - جميع العلامات من المنشور
  • عدد القراءة
  • المؤلفة
  • النتيجة - تصنيف المادة.

وبالتالي ، باستخدام API ، قللت من وقت تنفيذ البرنامج النصي إلى 8 ثوانٍ لكل 100 عنوان url.

بعد تنزيل البيانات التي نحتاجها ، نحتاج إلى معالجتها وإدخالها في قاعدة البيانات. لم أواجه أي مشاكل مع هذا أيضًا:

المحلل اللغوي

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 512 مقالة فقط. اتضح أن أكثر من نصف (261894) مقالات عن حبري قد تم إخفاؤها أو حذفها.
  • قاعدة البيانات بأكملها ، التي تتكون من نصف مليون مقال تقريبًا ، تزن 2.95 جيجا بايت. في شكل مضغوط - 495 ميغا بايت.
  • في المجموع ، 37804 شخصًا هم مؤلفو حبري. أذكرك أن هذه الإحصائيات مأخوذة فقط من المنشورات الحية.
  • المؤلف الأكثر إنتاجية في حبري - اليزار - 8774 مقالة.
  • المادة الأعلى تقييما - 1448 إيجابيات
  • الأكثر قراءة المقال - 1660841 مشاهدة
  • المادة الأكثر مناقشة - 2444 تعليقًا

حسنًا ، في شكل قممأفضل 15 مؤلفًاكل هبر في قاعدة بيانات واحدة
أفضل 15 حسب التصنيفكل هبر في قاعدة بيانات واحدة
أعلى 15 قراءةكل هبر في قاعدة بيانات واحدة
ناقش أعلى 15كل هبر في قاعدة بيانات واحدة

المصدر: www.habr.com

إضافة تعليق