코드 작성환경은 구름ide 우분투 18.0 버전에서 작성했고, python3 입니다.
2020/01/04 - [Python] - [Python] lxml, request 조합으로 뉴스기사 크롤링하기 -1
이번엔 1편에서 작성한 코드를 바탕으로 multiprocessing 을 통해 속도를 높여봅시다.
일단 앞서 작성한 코드들을 관리하기위해 함수단위로 분할해주었습니다.
time함수를 쓰기위해 time을 import합시다.
import time
def getParserPage(page):
url = 'https://news.joins.com/sports/baseball/list/' + str(page) + '?filter=All'
res = requests.get(url)
parser = fromstring(res.text)
return parser
def getArticles(parser):
article_list = parser.xpath('//div[@class="list_basic"]')
parsed_articles = article_list[0].xpath('.//li')
return parsed_articles;
def getLinks(parsed_articles):
links = []
for article in parsed_articles:
parsed_link = article.xpath('.//a[@href]')
link = parsed_link[0].get('href')
links.append(link)
return links;
def processArticles(links):
articles = [];
for link in links:
url = 'https://news.joins.com'
new_url = url + link;
res = requests.get(new_url)
parser = fromstring(res.text)
article_form = parser.xpath('//div[@id="body"]')[0]
subject = article_form.xpath('.//div[@class="subject"]/h1/text()')[0]
article_body = article_form.xpath('.//div[@id="article_body"]')[0].text_content()
article_content = article_body.replace('\xa0','\n')
article = {'title' : subject,'link' : new_url, 'content' : article_content}
articles.append(article)
return articles
그리고 이제 multiprocessing 에 사용할 함수를 만들어줍니다.
def crawlingNews(page):
while(True):
parser = getParserPage(page)
p_arts = getArticles(parser)
if (len(p_arts) == 0):
break
links = getLinks(p_arts)
articles = processArticles2(links)
print('page %d' % (page));
page += 8; #process의 개수
main 에서 이제 실행해 봅시다. process의 개수는 대다수가 I/O 작업에서 많은 시간을 쓰기에 8개로 크게 잡았습니다.
if __name__ == '__main__':
pages = [i+1 for i in range(8)];
pool = Pool(8) #processes = 8
start = time.time()
pool.map(crawlingNews,pages);
end = time.time()
print(end - start)
pool.close()
pool.join()
4page * 20 + 1 = 81개의 기사를 파싱하는데 14.68초가 걸렸군요.
0.18s/1개 정도의 속도입니다.
multiprocessing이 없었을경우는 속도가 어땠을까요?
단순히 처리부분을 제거하고 html을 요청하는데 걸리는 시간을 재봤습니다.
평균 0.5s/1개의 속도네요. 약 2.7배의 성능 향상이 있었습니다!
하지만 여기서 욕심을 더부려서 조금더 속도를 높일수 있을까요?
크롤링의 대부분의 시간은 I/O 작업에서 보냅니다. 코드를 살펴보면 processArticles 에서 가장많이 I/O에 시간을 보낸다는걸 알수있습니다. ( links의 길이만큼 request를 하기때문에 )
그러니 우리는 이부분만 threading을 통해 작업해봅시다.
파이썬에서 multithreading을 하면서 반환값을 어떻게 줘야되지 고민하고 있었는데 c++의 future같은 녀석을
지원해주고 있었습니다.
import concurrent.futures
이제 link를 얻는 부분만 함수화 해봅시다.
def getTextUrl(link):
url = 'https://news.joins.com'
new_url = url + link;
res = requests.get(new_url)
return {'content' : res.text, 'link' : new_url}
processArticles도 바꿉시다.
def processArticles(infos):
articles = [];
for info in infos:
parser = fromstring(info['content'])
article_form = parser.xpath('//div[@id="body"]')[0]
subject = article_form.xpath('.//div[@class="subject"]/h1/text()')[0]
article_body = article_form.xpath('.//div[@id="article_body"]')[0].text_content()
article_content = article_body.replace('\xa0','\n')
article = {'title' : subject,'link' : info['link'], 'content' : article_content}
articles.append(article)
return articles
마지막으로 crawlingNews도 future를 다루는 코드를 추가합시다.
def crawlingNews(page):
while(True):
parser = getParserPage(page)
p_arts = getArticles(parser)
if (len(p_arts) == 0):
break
links = getLinks(p_arts)
infos = []
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for link in links:
future = executor.submit(getTextUrl, link);
futures.append(future)
for future in futures:
infos.append(future.result())
articles = processArticles(infos)
print('page %d' % (page));
page += 8;
(찾아보니 async도 있던데 links 길이가 20이라 future보다 async가 더 효율적일수도 있습니다.)
성공적이네요! 실행 시간이 14.68s -> 6.74s 로 줄어들었습니다.
속도는 0.08s/1개 로 약 2.25배, 총 2.25 * 2.7 = 6.07배 정도의 성능 향상이 생겼습니다.
다음글에서는 하나의 Crawler 객체로 만들고 db, excel 저장 기능을 부여해 봅시다.
댓글은 언제나 환영합니다.
'Python' 카테고리의 다른 글
[Python] lxml, request 조합으로 뉴스기사 크롤링하기 -3 (0) | 2020.01.05 |
---|---|
[Python] lxml, request 조합으로 뉴스기사 크롤링하기 -1 (0) | 2020.01.04 |
[Python] argument를 bind하려면 ?? (partial) (0) | 2020.01.04 |
[Python] BeautifulSoup 으로 크롤링하기 (0) | 2019.11.10 |
댓글