शुभ दुपार. लिहून २ वर्षे झाली. शेवटचा लेख Habr पार्स करण्याबद्दल, आणि काही मुद्दे बदलले आहेत.
जेव्हा मला Habr ची एक प्रत हवी होती, तेव्हा मी एक पार्सर लिहिण्याचे ठरवले जे लेखकांची सर्व सामग्री डेटाबेसमध्ये जतन करेल. हे कसे घडले आणि मला कोणत्या त्रुटी आल्या - आपण कट अंतर्गत वाचू शकता.
सुरुवातीला, मी एक स्क्रिप्ट प्रोटोटाइप बनवण्याचा निर्णय घेतला ज्यामध्ये लेखाचे विश्लेषण केले जाईल आणि डाउनलोड केल्यानंतर लगेच डेटाबेसमध्ये ठेवले जाईल. दोनदा विचार न करता, मी 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 तास लागले.
पुढे मला वापरकर्त्याचा लेख सापडला एकत्रित, जे मी वाचले आणि या प्रक्रियेला गती देण्यासाठी काही लाइफ हॅक सापडले:
मल्टीथ्रेडिंग वापरल्याने काही वेळा डाउनलोड होण्याचा वेग वाढतो.
आपण habr ची संपूर्ण आवृत्ती मिळवू शकत नाही, परंतु त्याची मोबाइल आवृत्ती मिळवू शकता.
उदाहरणार्थ, डेस्कटॉप आवृत्तीमध्ये एकत्रित लेखाचे वजन 378 KB असल्यास, मोबाइल आवृत्तीमध्ये ते आधीच 126 KB आहे.
दुसरी आवृत्ती. अनेक धागे, हब्रीतून तात्पुरती बंदी
जेव्हा मी पायथनमधील मल्टीथ्रेडिंग विषयावर इंटरनेट शोधले, तेव्हा मी multiprocessing.dummy सह सर्वात सोपा पर्याय निवडला, मला लक्षात आले की मल्टीथ्रेडिंगसह समस्या दिसून आल्या.
SQLite3 एकापेक्षा जास्त थ्रेडसह कार्य करू इच्छित नाही.
निश्चित check_same_thread=False, परंतु ही त्रुटी केवळ एकच नाही, डेटाबेसमध्ये टाकण्याचा प्रयत्न करताना, कधीकधी त्रुटी उद्भवतात ज्या मी सोडवू शकत नाही.
म्हणून, मी थेट डेटाबेसमध्ये लेखांचा त्वरित समावेश सोडून देण्याचा निर्णय घेतो आणि एकत्रित समाधान लक्षात ठेवून, मी फायली वापरण्याचा निर्णय घेतो, कारण फाइलवर मल्टी-थ्रेडेड लेखनात कोणतीही समस्या नाही.
Habr तीन पेक्षा जास्त थ्रेड वापरण्यासाठी बंदी घालण्यास सुरुवात करते.
विशेषत: हॅब्रमध्ये जाण्याचा आवेशपूर्ण प्रयत्न काही तासांसाठी आयपी बंदीसह समाप्त होऊ शकतात. तर तुम्हाला फक्त 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 मध्ये अचानक एक 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
मी प्रत्येक लेखाचा पूर्ण json टाकला नाही, परंतु मला आवश्यक असलेली फील्ड जतन केली:
id
is_tutorial
time_published
शीर्षक
सामग्री
टिप्पण्या_गणना
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) Habré वरील लेख लपविले गेले किंवा हटवले गेले.
जवळजवळ अर्धा दशलक्ष लेखांचा समावेश असलेल्या संपूर्ण डेटाबेसचे वजन 2.95 GB आहे. संकुचित स्वरूपात - 495 एमबी.
एकूण, 37804 लोक Habré चे लेखक आहेत. मी तुम्हाला आठवण करून देतो की ही आकडेवारी केवळ थेट पोस्टमधून आहे.
Habré वरील सर्वात उत्पादक लेखक - alizar - 8774 लेख.