[FAQ] 전화번호부 사이트 크롤링

전화번호부 사이트 크롤링

  • Q: 전화번호부 사이트를 검색하여 목록을 추출하고자 하는데, 데이터 전달이 되지 않습니다.
  • A: 데이터를 인코딩 해주세요. 국내 사이트의 경우 종종 EUC-KR로 인코딩된 데이터만 수용하도록 설계된 경우가 있습니다.
http://fb.com/financedata

전화번호부 사이트

http://www.isuperpage.co.kr

지역과 범주를 입력하고 검색하면,

다음과 같은 검색 결과를 얻을 수 있다. (특정 지역 + 휴일진료하는 의료기관을 알 수 있다면 좋은 정보가 될 수 도 있을 듯)

euc-kr 인코딩 문제

다른 크롤링 방법과 크게 다르지 않다. 다만, 폼 데이터(requests.post()의 data 인자)로 서버에 전달해야 하는 값이 유니코드가 아니라 "euc-kr"이라는 점이 차이가 있다. (국내 사이트의 상당수가 이런 인코딩 이슈를 안고 있다)

빨간색 테두리 부분을 살펴보자. FormData 부분을 보면 이상한 문자열로 데이터가 구성되는 것을 볼 수 있는데. 16진수 데이터들이 URL인코딩된 데이터이다.

In [1]:
# searchWord:%C8%DE%C0%CF%C1%F8%B7%E1
# x:37.5640907
# y:126.9979403
# dong:
# city:%BC%AD%BF%EF
# gu:%C1%DF%B1%B8
# addr4:
# addr:%BC%AD%BF%EF+%C1%DF%B1%B8

from urllib.parse import quote, unquote

u = '%C8%DE%C0%CF%C1%F8%B7%E1'
print (unquote(u, encoding='euc-kr'))

u = '%BC%AD%BF%EF'
print (unquote(u, encoding='euc-kr'))

u = '%BC%AD%BF%EF+%C1%DF%B1%B8'
print (unquote(u, encoding='euc-kr'))
휴일진료
서울
서울+중구

str(유니코드) → bytes(EUC-KR) → URL Quot

In [2]:
b = '휴일진료'.encode('euc-kr')
print( type(b) )
print( quote(b) )
<class 'bytes'>
%C8%DE%C0%CF%C1%F8%B7%E1

URL Quot (EUC-KR) → str(유니코드)

In [3]:
u = '%C8%DE%C0%CF%C1%F8%B7%E1'
s = unquote(u, encoding='euc-kr')
print(type(s))
print(s)
<class 'str'>
휴일진료

해결책

해결책은 무척 간단하다. str(유니코드)를 EUC-KR로 인코딩을 해주면 된다.

str.encode('euc-kr')

크롤링과 한글 인코딩에 대한 조금 더 상세한 내용은 아래 포스팅을 참조하자.

In [4]:
import requests

url = 'http://www.isuperpage.co.kr/search.asp'

data = {
    'searchWord': '휴일진료'.encode('euc-kr'),
    'dong':''.encode('euc-kr'),
    'city': '서울'.encode('euc-kr'),
    'gu': '중구'.encode('euc-kr'),
}

r = requests.post(url, data=data)
r.text[:1000]
Out[4]:
'\r\n<!DOCTYPE html>\r\n<html lang="ko">\r\n<head>\r\n<meta charset="euc-kr">\r\n<meta http-equiv="X-UA-Compatible" content="IE=edge" />\r\n<title>:: 대한민국 모든 전화번호 검색은 한국전화번호부 ::</title>\r\n<link rel="stylesheet" type=\'text/css\' href="/css3/base_n.css"/>\r\n<link href="/css3/dcaccordion.css" rel="stylesheet" type="text/css" />\r\n<link href="/jqy/jquery-ui.css" rel="stylesheet" type="text/css" />\r\n<script type="text/javascript" src="/jqy/jquery-1.11.2.min.js"></script>\r\n<script src="/jqy/jquery-ui-1.10.3.custom.min.js"></script>\r\n<script src="/jqy/jquery.ba-dotimeout.min.js"></script>\r\n<script src="/jqy/common_web.js"></script>\r\n<script src="/jqy/placeholders.min.js"></script>\r\n\r\n  \r\n\r\n\r\n<script language="javascript">\r\n\r\n\r\n$(function() {\r\n\t$("img.lmimg").mouseover(function() {\r\n\t\r\n\t\t$(this).attr("src",$(this).attr("src").replace("off","on"));\r\n\t});\r\n  $("img.lmimg").mouseout(function() {\r\n        $(this).css("display","");\r\n\t\t$(this).attr("src",$(this).attr("src").replace("on","off"));\r\n\t});\t\r\n\t\r\n});\r\n\r\nf'
In [5]:
from bs4 import  BeautifulSoup

soup = BeautifulSoup(r.text, 'lxml')
search_result = soup.find('div', {'id':'search_result'})
In [6]:
lis = search_result.find_all('li')[2:]
for li in lis:
    divs = li.find_all('div')

    # div[0]
    title = divs[0].a.text # 상호
    spans = divs[0].find_all('span')
    search = spans[1].text # 검색어

    # div[1]
    spans = divs[1].find_all('span') 
    phone = spans[0].text # 전화번호
    addr = spans[1].text  # 주소
    addr_road = spans[2]['title']  # 도로명 주소
    
    print( "%s, %s, %s, %s, %s" % (title, search, phone, addr, addr_road) )
중정한의원, 휴일진료병원, 02-3789-7017, 서울 중구 명동1가 7-1, 서울 중구 명동길 55
신농백초한의원, 휴일진료병원, 02-736-4644, 서울 중구 정동 1-48, 서울 중구 세종대로21길 53
서울백병원, 휴일진료병원, 02-2270-0119, 서울 중구 저동 2가 85, 서울 중구 
서울내과의원, 휴일진료병원, 02-2256-7575, 서울 중구 신당6동 294-13, 서울 중구 
반도병원, 휴일진료병원, 02-2252-0202, 서울 중구 신당2동 422-4, 서울 중구 
하버드마취통증의학과의원, 휴일진료병원, 02-2236-8188, 서울 중구 무학동 1, 서울 중구 다산로 245
청구경희한의원, 휴일진료병원, 02-2236-1075, 서울 중구 신당4동 309-8, 서울 중구 
유태석내과의원, 휴일진료병원, 02-2233-7076, 서울 중구 신당3동 373-3, 서울 중구 
명동밀리오레산부인과의원, 휴일진료병원, 02-2124-0330, 서울 중구 충무로1가 24-1, 서울 중구 퇴계로 115
장충동자생한의원, 휴일진료병원, 02-2234-0700, 서울 중구 장충동2가 202, 서울 중구 동호로 249

댓글

Comments powered by Disqus