파이썬으로 크롤링 하기

hans mj
7 min readJun 19, 2017

--

requests와 Beautiful Soup을 사용해보기

크롤링의 정의는 “크롤링(crawling) 혹은 스크래이핑(scraping)은 웹 페이지를 그대로 가져와서 거기서 데이터를 추출해 내는 행위” 이다.(나무위키 발췌)

파이썬은 크롤링을 위한 모듈이 꽤 잘 만들어져 있는 편이라 쉽게 할수 있다. 누구나 쉽게 할 수있는 것이라 해도 막상 내가 하면 잘 안되는 법!! 이번 기회에 크롤링 하는 법을 정리해보고자 한다.

크롤링 또는 스크래이핑 방법

  • 원하는 웹 페이지에 request를 보내 결과 html을 받는다.
  • 받은 html을 파싱한다.
  • 필요한 정보만 추출한다.

파이썬을 이용해서 웹 크롤러를 만들기 위해서는 http request / response를 다루는 모듈과, html을 파싱하는 모듈이 필요하다.

  1. requests 모듈

http request를 다루기 위해서 requests모듈이 있다. 파이썬에 내장된 urllib 모듈을 편하게 사용하도록 만든 것이다. 설치하는 방법은 pip를 이용하면 된다.

pip install requests

requests모듈 공식 홈페이지에 나와있는 사용법이다. 사용하기 매우 단순해 보인다.

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json()
{u'private_gists': 419, u'total_private_repos': 77, ...}

아래와 같이 함수를 만들어두고 사용하도록 하겠다.

def get_html(url):
_html = ""
resp = requests.get(url)
if resp.status_code == 200:
_html = resp.text
return _html

2. Beautiful soup

많이 쓰이는 파이썬용 파서로, html, xml을 파싱할때 주로 많이 사용한다. lxml을 사용하면 성능 향상이 있다고 공식 홈페이지에 나와있다.

pip install beautifulsoup4

사용법 역시 간단하다.

from bs4 import BeautifulSoup
URL = "http://comic.naver.com/webtoon/list.nhn?titleId=20853&weekday=tue&page=1"
html = get_html(URL)
soup = BeautifulSoup(html, 'html.parser')

미리 만들어어둔 get_html 함수를 이용해 html을 얻은 후 beautifulSoup을 이용해서 파싱객체를 생성한다. 위의 예에서는 soup이라는 객체가 파싱결과를 담고 있다.

soup객체는 내가 필요한 부분을 쉽게 찾을수 있도록 find, find_all함수를 제공한다. 위의 결과에서 a 태그를 모두 찾기 위해서 soup.find_all(“a”)라는 함수만 실행하면 된다.

>>> l = soup.find_all("a")
>>> print(len(l))
127

원하는 정보가 html문서 어느 위치에 있는지 확인하려면, 크롬의 소스보기를 통해서 파악해야 한다.

3. 샘플프로그램 만들기

샘플 프로그램으로 마음의 소리 페이지를 파싱하고, 결과를 레디스에 저장해보려고 한다.(샘플 프로그램은 이곳의 아이디어를 참고했다.)

  • 마음의 소리 목록 갖고 오기

우선 마음의 소리 웹툰의 페이지열고 소스보기를 통해서 제목과 회차가 나와있는 위치를 확인한다.

확인결과 table-> tbody-> tr -> td에 위치 하고 있는 것으로 보인다. html문서에서 한번에 결과를 얻어오려면 복잡해지기 때문에 table태그의 class가 viewList인 html조각 객체를 얻어온다.

webtoon_area = soup.find("table",{"class": "viewList"})

이제 class가 viewList인 table 태그의 bs객체를 얻었다. 이 객체에 포함되어 있는 td 태그에 class가 title 인 모든 객체를 얻기 위해서는 find_all함수를 사용하면 된다.

webtoon_area = soup.find("table",
{"class": "viewList"}
).find_all("td", {"class":"title"})

find_all은 결과를 리스트로 반환하므로, for ~in 문으로 순회하면서 원하는 처리를 하면 된다.

위와같이 해서 마음의 소리 회차, 제목, url을 튜플로 만들어 리스트에 저장한다.

def insert_webtoon_info(simple_redis, infos):
for info in infos:
res = simple_redis.redis_hash_set("maso", info[0], info)

만들어진 리스트를 순회하면서 레디스 hash에 저장하면 샘플 프로그램은 완성된다.

전체 소스는 github에 올렸다.

  • 성능 이슈

RedisQ에서 페이지 번호를 1~100까지 저장하고, pop하면서 속도를 측정했을때, 11초 정도 걸렸다. http Request시 I/O발생하면서, 멈추게 된다. 이런 현상을 해결하기 위해서는 멀티 쓰레드를 사용하면 되지만, 하나의 쓰레드에서 I/O가 발생해야 다음 쓰레드가 실행되는 구조이고, 각각의 쓰레드에 I/O가 발생하면 다음 작업이 수행 되는 것이 아니라 대기 하게 된다.

Non-bloking구조로 프로그램을 만들어 I/O가 발생하면 멈추는 것이 I/O가 발생한 지점을 기억해두고, 다음 작업을 수행하고, I/O가 풀리면 저장된 지점부터 프로그램을 다시 실행하는 것이다. 이것을 위해서 파이썬 3.4부터 asyncio가 추가 되었다.

기본개념부터 이해하려면, 제너레이터, 코루틴 등부터 제대로 알아야 하지만, 다음 포스팅에서는 이번에 만든 샘플프로그램을 asyncio로 다시 만들어보면서 일단 실행을 시켜보려고한다.

--

--