์ข์ ์คํ์์. ์ด์ง 2๋
์ด ๋์์ต๋๋ค.
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)
๋ชจ๋ ๊ฒ์ด ๊ณ ์ ์ ์ ๋๋ค. Beautiful Soup, ์์ฒญ ๋ฐ ๋น ๋ฅธ ํ๋กํ ํ์ ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๊ฑด ๊ทธ๋ฅโฆ
-
ํ์ด์ง ๋ค์ด๋ก๋๊ฐ ํ๋์ ์ค๋ ๋์ ์์
-
์คํฌ๋ฆฝํธ ์คํ์ ์ค๋จํ๋ฉด ์ ์ฒด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์๋ฌด๋ฐ๋ ๊ฐ์ง ์์ต๋๋ค. ๊ฒฐ๊ตญ ์ปค๋ฐ์ ๋ชจ๋ ๊ตฌ๋ฌธ ๋ถ์ ํ์ ์ํ๋ฉ๋๋ค.
๋ฌผ๋ก ์ฝ์ ํ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ณ๊ฒฝ ์ฌํญ์ ์ปค๋ฐํ ์ ์์ง๋ง ์คํฌ๋ฆฝํธ ์คํ ์๊ฐ์ด ํฌ๊ฒ ๋์ด๋ฉ๋๋ค. -
์ฒ์ 100๊ฐ์ ๊ธฐ์ฌ๋ฅผ ํ์ฑํ๋ ๋ฐ 000์๊ฐ์ด ๊ฑธ๋ ธ์ต๋๋ค.
๋ค์์ผ๋ก ์ฌ์ฉ์์ ๊ธฐ์ฌ๋ฅผ ์ฐพ์ต๋๋ค.
- ๋ฉํฐ์ค๋ ๋ฉ์ ์ฌ์ฉํ๋ฉด ๋๋๋ก ๋ค์ด๋ก๋ ์๋๊ฐ ๋นจ๋ผ์ง๋๋ค.
- habr์ ์ ์ ๋ฒ์ ์ด ์๋ ๋ชจ๋ฐ์ผ ๋ฒ์ ์ ์ป์ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ๋ฐ์คํฌํฑ ๋ฒ์ ์์ ๊ณต๋ ํตํฉ๋ ๊ธฐ์ฌ์ ๋ฌด๊ฒ๊ฐ 378KB์ธ ๊ฒฝ์ฐ ๋ชจ๋ฐ์ผ ๋ฒ์ ์์๋ ์ด๋ฏธ 126KB์ ๋๋ค.
๋ ๋ฒ์งธ ๋ฒ์ . ๋ง์ ์ค๋ ๋, Habr์์ ์ผ์์ ์ธ ๊ธ์ง
ํ์ด์ฌ์ ๋ฉํฐ์ค๋ ๋ฉ ์ฃผ์ ์ ๋ํด ์ธํฐ๋ท์ ์ ์ ์ด ๋ค์ก์ ๋ multiprocessing.dummy๋ก ๊ฐ์ฅ ๊ฐ๋จํ ์ต์ ์ ์ ํํ๋๋ฐ ๋ฉํฐ์ค๋ ๋ฉ๊ณผ ํจ๊ป ๋ฌธ์ ๊ฐ ๋ํ๋๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค.
SQLite3๋ ๋ ์ด์์ ์ค๋ ๋์์ ์์
ํ๊ธฐ๋ฅผ ์ํ์ง ์์ต๋๋ค..
๊ฒฐ์ ๋ check_same_thread=False
,ํ์ง๋ง์ด ์ค๋ฅ๊ฐ ์ ์ผํ ๊ฒ์ด ์๋๋ผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฝ์
ํ๋ ค๊ณ ํ ๋ ๋๋๋ก ํด๊ฒฐํ ์์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
๋ฐ๋ผ์ ๋๋ ๊ธฐ์ฌ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ฝ์ ํ๋ ๊ฒ์ ํฌ๊ธฐํ๊ณ ๊ณต๋ ํตํฉ ์๋ฃจ์ ์ ๊ธฐ์ตํ๋ฉด์ ํ์ผ์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ํ์ผ์ ๋ํ ๋ค์ค ์ค๋ ๋ ์ฐ๊ธฐ์ ๋ฌธ์ ๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
Habr, ์ฐ๋ ๋ XNUMX๊ฐ ์ด์ ์ฌ์ฉ ๊ธ์ง ์์.
ํนํ 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์ ์ฌ์ดํธ์ ๋ชจ๋ฐ์ผ ๋ฒ์ ์ด ์ก์ธ์คํ๋ API๊ฐ ์์์ ๋ฐ๊ฒฌํ์ต๋๋ค. ํ์ฑํ ํ์์กฐ์ฐจ ์๋ json์ด๊ธฐ ๋๋ฌธ์ ๋ชจ๋ฐ์ผ ๋ฒ์ ๋ณด๋ค ๋น ๋ฅด๊ฒ ๋ก๋๋ฉ๋๋ค. ๊ฒฐ๊ตญ ๋๋ ๋๋ณธ์ ๋ค์ ์ฐ๊ธฐ๋ก ํ๋ค.
๊ทธ๋์, ์ฐพ์
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
- ์๊ฐ_๊ฒ์๋จ
- ์ ๋ชฉ
- ํจ์ ๋
- ๋๊ธ_๊ฐ์
- lang์ ๊ธฐ์ฌ๊ฐ ์์ฑ๋ ์ธ์ด์ ๋๋ค. ์ง๊ธ๊น์ง๋ en๊ณผ ru๋ง ์์ต๋๋ค.
- tags_string - ๊ฒ์๋ฌผ์ ๋ชจ๋ ํ๊ทธ
- ๋ ์ ํ์
- ์ ์
- ์ ์ โ ๊ธฐ์ฌ ๋ฑ๊ธ.
๊ทธ๋์ API๋ฅผ ์ด์ฉํด์ URL 8๊ฐ๋น ์คํฌ๋ฆฝํธ ์คํ์๊ฐ์ 100์ด๋ก ์ค์์ต๋๋ค.
ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ํ ํ ์ฒ๋ฆฌํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๋ ฅํด์ผ ํฉ๋๋ค. ๋๋ ์ด๊ฒ์๋ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
ํ์.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๋ง ๊ฐ์ ๊ธฐ์ฌ๋ก ๊ตฌ์ฑ๋ ์ ์ฒด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฌด๊ฒ๋ 495GB์ ๋๋ค. ์์ถ๋ ํ์ - XNUMXMB.
- ์ด 37804๋ช ์ด Habrรฉ์ ์๊ฐ์ ๋๋ค. ์ด ํต๊ณ๋ ๋ผ์ด๋ธ ๊ฒ์๋ฌผ์์๋ง ๊ฐ์ ธ์จ ๊ฒ์์ ์๋ ค๋๋ฆฝ๋๋ค.
- Habrรฉ์์ ๊ฐ์ฅ ์์ฐ์ ์ธ ์๊ฐ -
์๋ฆฌ์๋ฅด - 8774๊ฐ์ ๊ธฐ์ฌ. ์ต๊ณ ํ์ ๊ธฐ์ฌ โ 1448 ํ๋ฌ์ค๊ฐ์ฅ ๋ง์ด ์ฝ์ ๊ธฐ์ฌ โ 1660841 ์กฐํ์๊ฐ์ฅ ๋ง์ด ๋ ผ์๋ ๊ธฐ์ฌ โ 2444๊ฐ์ ๋๊ธ
๊ธ์,์์์ ํํ๋ก์์ 15๋ช
์ ์ ์
ํ๊ฐ ๊ธฐ์ค ์์ 15์
์์ 15๊ฐ ์ฝ๊ธฐ
๋
ผ์๋ ์์ 15๊ฐ
์ถ์ฒ : habr.com