Feasgar math. Tha 2 bhliadhna air a bhith ann bho chaidh a sgrìobhadh. artaigil mu dheireadh mu bhith a’ parsadh Habr, agus tha cuid de phuingean air atharrachadh.
Nuair a bha mi airson leth-bhreac de Habr a bhith agam, chuir mi romham parser a sgrìobhadh a shàbhaileadh susbaint nan ùghdaran gu lèir don stòr-dàta. Mar a thachair e agus dè na mearachdan a thachair mi - faodaidh tu leughadh fon ghearradh.
A 'chiad dreach den parser. Aon snàithlean, mòran dhuilgheadasan
An toiseach, chuir mi romham prototype sgriobt a dhèanamh, anns am biodh an artaigil air a pharsadh sa bhad nuair a chaidh a luchdachadh sìos agus a chuir san stòr-dàta. Gun a bhith a 'smaoineachadh dà uair, chleachd mi sqlite3, oir. cha robh e cho dian air saothair: cha robh feum air frithealaiche ionadail, cruthaichte-sguabadh às agus stuth mar sin.
aon_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)
Tha a h-uile dad clasaigeach - bidh sinn a’ cleachdadh Beautiful Soup, iarrtasan agus tha prototype sgiobalta deiseil. Tha sin dìreach…
Tha luchdachadh sìos duilleag ann an aon snàithlean
Ma chuireas tu bacadh air coileanadh an sgriobt, cha tèid an stòr-dàta gu lèir gu àite sam bith. Às deidh na h-uile, chan eil an gealladh air a dhèanamh ach às deidh a h-uile parsadh.
Gu dearbh, faodaidh tu atharrachaidhean a dhèanamh air an stòr-dàta às deidh gach cuir a-steach, ach an uairsin meudaichidh ùine cur an gnìomh an sgriobt gu mòr.
Thug parsadh a’ chiad 100 artaigil 000 uairean dhomh.
An uairsin lorg mi artaigil an neach-cleachdaidh co-aonaichte, a leugh mi agus a lorg mi beagan hacks beatha gus am pròiseas seo a luathachadh:
Bidh cleachdadh multithreading a’ luathachadh luchdachadh sìos aig amannan.
Chan fhaigh thu an dreach slàn den habr, ach an dreach gluasadach aige.
Mar eisimpleir, ma tha cuideam 378 KB ann an artaigil co-aonaichte san dreach deasg, an uairsin anns an dreach gluasadach tha e mu thràth 126 KB.
An dàrna tionndadh. Mòran snàithlean, casg sealach bho Habr
Nuair a rinn mi sgùradh air an eadar-lìn air cuspair multithreading ann am python, thagh mi an roghainn as sìmplidh le multiprocessing.dummy, mhothaich mi gun do nochd duilgheadasan còmhla ri multithreading.
Chan eil SQLite3 airson a bhith ag obair le barrachd air aon snàithlean.
stèidhichte check_same_thread=False, ach chan e am mearachd seo an aon fhear, nuair a tha mi a’ feuchainn ri cuir a-steach don stòr-dàta, bidh mearachdan uaireannan a’ tachairt nach b’ urrainn dhomh fhuasgladh.
Mar sin, tha mi a 'co-dhùnadh a bhith a' trèigsinn cuir a-steach artaigilean gu dìreach a-steach don stòr-dàta agus, a 'cuimhneachadh air an fhuasgladh co-fhillte, tha mi a' co-dhùnadh na faidhlichean a chleachdadh, oir chan eil duilgheadasan ann le sgrìobhadh ioma-snàithlean gu faidhle.
Bidh Habr a 'tòiseachadh a' casg airson barrachd air trì snàithleanan a chleachdadh.
Faodaidh oidhirpean gu sònraichte dùrachdach faighinn troimhe gu Habr crìoch a chuir air casg ip airson dà uair a thìde. Mar sin feumaidh tu dìreach 3 snàithleanan a chleachdadh, ach tha seo math mar-thà, leis gu bheil an ùine airson ath-aithris còrr air 100 artaigil air a lughdachadh bho 26 gu 12 diogan.
Is fhiach a bhith mothachail gu bheil an dreach seo caran neo-sheasmhach, agus bidh luchdachadh sìos bho àm gu àm a ’tuiteam air àireamh mhòr de artaigilean.
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)
Treas dreach. Deireannach
Fhad ‘s a bha mi a’ dì-bhugachadh an dàrna dreach, fhuair mi a-mach gu bheil API aig Habr, gu h-obann, a gheibh an dreach gluasadach den làrach. Bidh e a’ luchdachadh nas luaithe na an dreach gluasadach, leis gur e dìreach json a th’ ann, nach eil eadhon feumach air parsadh. Aig a’ cheann thall, chuir mi romham mo sgriobt ath-sgrìobhadh a-rithist.
Mar sin, an dèidh lorg an ceangal seo API, faodaidh tu tòiseachadh air a pharsadh.
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)
Tha raointean ann co-cheangailte ris an artaigil fhèin agus ris an ùghdar a sgrìobh e.
API.png
Cha do chuir mi sìos json slàn gach artaigil, ach cha do shàbhail mi ach na raointean a bha a dhìth orm:
id
is_tutorial
uair_foillsichte
tiotal a 'Chlàir
clàr na làraich
beachdan_cunntadh
lang an cànan anns a bheil an artaigil sgrìobhte. Gu ruige seo, chan eil ann ach en agus ru.
tags_string - a h-uile taga bhon phost
leughadh_cunnt
ùghdar
sgòr - rangachadh artaigil.
Mar sin, a’ cleachdadh an API, lughdaich mi ùine cur an gnìomh an sgriobt gu 8 diogan gach 100 url.
Às deidh dhuinn an dàta a tha a dhìth oirnn a luchdachadh sìos, feumaidh sinn a phròiseasadh agus a chuir a-steach don stòr-dàta. Cha robh duilgheadas sam bith agam le seo idir:
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)
Статистика
Uill, gu traidiseanta, mu dheireadh, faodaidh tu cuid de staitistig a tharraing bhon dàta:
De na 490 luchdachadh sìos ris an robh dùil, cha deach ach 406 artaigil a luchdachadh sìos. Tha e coltach gun deach còrr air leth (228) de na h-artaigilean air Habré fhalach no sguabadh às.
Tha an stòr-dàta gu lèir, anns a bheil faisg air leth mhillean artaigil, le cuideam 2.95 GB. Ann an cruth teann - 495 MB.
Gu h-iomlan, tha 37804 neach nan ùghdaran aig Habré. Tha mi gad chuimhneachadh nach eil na staitistig sin ach bho phuist beò.
An t-ùghdar as cinneasaiche air Habré - alizar — 8774 alt.