Ruby Programming 8 Web Crawling 한국어정보의전산처리 2017. 5. 24.
Web Crawling 의요소기술 웹에접속하여웹문서읽어오기 open-uri 나 net/http 라이브러리이용. 웹문서분석 xml/html parser 라이브러리 ( 예 : nokogiri) 를이용할수도있으나 간단한분석일때는 scan 등의함수로정규표현을검색하여처리할수도있음. 날짜처리 url 이날짜를바탕으로할때는 Date 클래스로날짜처리. 반복 / 재귀알고리즘 수많은웹페이지를방문할때에는 iterative 또는 recursive 알고리즘을사용해야함.
조선일보스포츠기사제목읽어오기 조선일보홈페이지 메뉴전체보기 를클릭하여 스포츠 - 전체 를선택 http://news.chosun.com/svc/list_in/se_list.html?catid=g1 날짜를클릭 http://news.chosun.com/svc/list_in/list_title.html?catid=g1&in date=20170524 하루당스포츠웹페이지가여러페이지로이루어져있고 한페이지당 20 개의기사요약이제시됨. 각기사의제목만추출하고자함. 조선일보홈페이지 html 파일의인코딩은 euc-kr 이므로 iconv 를이용하여출력결과를 utf-8 로변환하여출력
기사의 html 구조 html 소스코드를보면, 각기사는다음과같은구조로되어있음. 붉은색부분만추출하고자함. <dl class="list_item"> <dt><a href="http://news.chosun.com/site/data/html_dir/2017/05/24/2017052400394.html"> [ 줌인 ] 김성근감독형식은자진사퇴, 내용은경질?</a></dt> <dd class="thumb"><a href="http://news.chosun.com/site/data/html_dir/2017/05/24/2017052 400394.html"><img src="http://image.chosun.com/sitedata/thumbnail/201705/24/201705240 0382_0_thumb.jpg" alt=""></a></dd> <dd class="desc"><a href="http://news.chosun.com/site/data/html_dir/2017/05/24/20170524 00394.html"> 김성근한화이글스감독이자진사퇴했고, 한화구단은사의를수용하며이상군투수코치를감독대행에임명했다. 김감독은지난 21 일대전삼성라이온즈전을마..</a></dd> <dd class="date_author"> <span class="date">2017.05.24 ( 수 )</span> <span class="author"> 스포츠조선 = 박재호기자 </span> </dd> </dl>
Ruby script 를바깥쪽부터작성함 사용자로부터시작날짜와끝날짜를입력받아, 각날짜의조선일보스포츠웹페이지를읽어옴. require 'date' require 'net/http' begin_date = Date.new(ARGV[0].to_i, ARGV[1].to_i, ARGV[2].to_i) _date = Date.new(ARGV[3].to_i, ARGV[4].to_i, ARGV[5].to_i) Net::HTTP.start("news.chosun.com") do http # 조선일보홈페이지에접속 begin_date.upto(end_date) do date # 시작날짜부터끝날짜까지 d = date.to_s.gsub("-","") # 날짜를문자열로 : 20170523 url = "/svc/list_in/list_title.html?catid=g1&indate=#{d}" # 해당날짜의스포츠기사웹주소 e = find_end_page(http, url, 1) # 해당날짜의스포츠기사의페이지수알아냄 (1..e).each do i #1 페이지부터끝페이지까지 search(http, url+"&pn=#{i}") #search 함수호출
find_end_page 함수 @end_pattern = %r <li><a style="text-decoration:none;cursor:de fault" class="current">[0-9]+</a></li><li><a style="text-decorat ion:none;cursor:default"> # 마지막페이지의특징을정규표현으로포착함. def find_end_page(http, url, n) # 스포츠기사가몇페이지까지있는지알아내는함수 str = http.get(url+"&pn=#{n}").body #n 번째페이지를읽어옴 if not str =~ @end_pattern # 마지막페이지가아니면 find_end_page(http, url, n+1) #find_end_page 함수재귀호출 else # 마지막페이지이면 return n #n 을반환하고함수종료
search 함수 @pattern = %r <dt><a href="http://news.chosun.com/site /data/html_dir/([0-9]{4}/[0-9]{2}/[0-9]{2})/[0-9]{13}\.h tml">(.*)</a></dt> def search(http, url) puts url #url 출력 str = http.get(url).body #url 의웹문서를불러옴 str.scan(@pattern).each do date, title # 웹문서에서정규표현 (pattern) 을찾음. # 괄호친두부분을 date, title 이라지칭 print date, "\t", title, "\n" #date 와 title 출력
위의코드의문제 해당날짜의스포츠웹페이지가몇페이지까지있는지알아내는함수 (find_end_page) 에서도각웹페이지의문서를읽어오고 각웹문서를읽어와서제목을추출하는함수 (search) 에서도각웹페이지의문서를읽어오게되어있어서 같은일을 2 번반복하는꼴이됨. 페이지수만큼반복하는 iterative algorithm 을적용하는셈. 각웹페이지문서를 1 번만읽어오면서과제를수행하기위해서는 recursive algorithm 이필요함. 날짜와일련번호를가지고 url 을만드는일은 search 함수에게맡기는게나음. ( 그러면 calling code 가더깔끔해짐.)
recursive algorithm 을적용한 calling code Net::HTTP.start("news.chosun.com") do http begin_date.upto(end_date) do date search(http, date, 1) # 첫째페이지에대해 search 함수호출
search 함수 def search(http, date, n) d = date.to_s.gsub("-","") # 날짜를문자열로 : 2017052 3 url = "/svc/list_in/list.html?catid=g1&indate=#{d}&p n=#{n}" puts url #url 출력 str = http.get(url).body str.scan(@pattern).each do date, title print date, "\t", title, "\n" #date 와 title 출력 search(http, date, n+1) if not str =~ @end_pattern # 마지막페이지가아니면 n 을 1 증가시켜함수재귀호출
동아일보 동아일보홈페이지, 스포츠, 최신기사, 날짜별로들어가보면, 전반적인구조는조선일보와비슷함. http://news.donga.com/list/sports/?ymd=20170524&m= 한페이지에 20 개의기사씩제시되고 url 에첫기사의일련번호 (1,21,41,...) 와날짜가포함됨. http://news.donga.com/list/sports/?p=21&prod=news&ymd=2 0170524&m= 조선일보와비슷하게 search 함수를재귀호출하되 이함수의 parameter 에일련번호도포함시켜야함.
동아일보용 calling 코드 @pattern = %r <div class='articlelist'><div class='rightl ist'><a href='http://news.donga.com/list/sports/[0-9]/[0-9]{2}/([0-9]{8})/[0-9]+/[0-9]'><span class='tit'>(.+?)</s pan> @end_pattern = %r </a><strong>[0-9]+</strong><a href ='\?p=none\&prod=news\&ymd=[0-9]{8}\&m=' class='r ight'> Net::HTTP.start("news.donga.com") do http begin_date.upto(end_date) do date search(http, date, 1) #search 함수호출
동아일보용 search 함수 def search(http, date, n) d = date.to_s.gsub("-","") # 날짜를문자열로 : 201705 24 url = "/List/Sports/?p=#{n}&prod=news&ymd=#{d} &m=" puts url #url 출력 str = http.get(url).body #url 의웹문서를불러옴 str.scan(@pattern).each do date, title print date, "\t", title, "\n" #date 와 title 출력 search(http, date, n+20) if not str =~ @end_pattern # 마지막페이지가아니면 search 함수재귀호출