본문 바로가기

Parsing to BBC News

Python을 통한 BBC 뉴스 크롤링하기

728x90

이전 글에서 설명한 것처럼 뉴스를 파싱해보도록 하자.

오늘 해볼 것은 아래와 같다.

 

1. 외국 사이트에서 문장을 크롤링(사실 파싱이 맞다)하여 화면에 표시(파싱)

1.1 Python(BeautifulSoup)을 통해 외국사이트에서 특정 키워드를 추출하여 배열로 return 하기

1.2 Python에서 리턴한 데이터를 node에서 받아와 GET 웹서비스로 만들기

1.3 React 기반 웹에서 node 서비스를 호출하고, 화면에 '예쁘게' 표시하기

 

 

굳이 파이썬을 통해 파싱할 필요는 없지만 추후 확장성을 고려하여 BeautifulSoup를 사용할 수 있는 Python을 선택했으나, 얼마든지 자바스크립트를 사용해도 된다.

 

우선 파싱할 페이지는

feeds.bbci.co.uk/news/rss.xml

 

이다.

 

BBC의 최신 뉴스를 받아 볼 수 있으며, 페이지의 구조가 매우 단순하여 파싱하기 좋다.

 

사이트의 외관은 이렇게 되어있고,

 

실질적인 소스는 이러한 구조로 되어있다.

 

여기서 우리가 필요한것은

 

 

이러한 뉴스 타이틀과 세부내용이다.

 

 

소스에서는 이렇게 되어있다.

즉 우리는 item 하위의 title과 description을 가져오면 되는 것이다.

 

이를 jQuery나 순수 자바스크립트로 어렵게 select 할 수도 있지만, 쉽고 간편한 BeautifulSoup를 사용하여 해결하도록 하자.

 

간단하니까 바로 소스를 보자.

소스는 3단계로 구성되어 있다.

1. 필수 모듈 import 및 기본 환경설정

2. 요청

3. 응답 데이터에서 데이터 필터링하여 출력

 

 

## 파싱을 위해 필요한 모듈을 import한다.
import requests #pip install requests 해야 사용 가능하다. 파이썬에서 http 요청을 보내기 위해서 사용한다.
import sys #화면에 표시할때 인코딩을 맞춰주기 위한 모듈이다.
import io #화면에 표시할때 인코딩을 맞춰주기 위한 모듈이다.
from bs4 import BeautifulSoup #pip install bs4 해야 사용 가능하다. 파싱한 데이터를 말 그대로 예쁘게 정리해주는 라이브러리이다.

sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8') #이부분이 없으면 콘솔에 출력하는것은 잘 되지만 노드로 데이터 전달이 안된다.
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8') #이부분이 없으면 콘솔에 출력하는것은 잘 되지만 노드로 데이터 전달이 안된다.

우선, 초기작업으로 필요한 모듈을 정의하고 인코딩을 맞춰준다.

 

 

 

# Session 생성
s = requests.Session()

# 헤더 설정
headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36'
}
    
req = s.get('http://feeds.bbci.co.uk/news/rss.xml',headers=headers)


## HTML 소스 가져오기
html = req.text

우선 서버에 데이터를 요청하기 위해 세션을 생성해야한다.

requests 모듈을 이용하여 쉽게 세션 생성이 가능하다.

또한 세션 내에 필요한 데이터들도 같이 넣어준다.

GET으로 URL을 보내고, headers를 넣어줄 것인데, 단순한 구조의 xml이 대상이므로 딱히 보낼 정보는 없고,

User-Agent만 넣어주면 되는데, 아래와 같이 실제로 사용자가 접속할때 보내는 정보와 동일하게 복사해서 보내주면 되겠다.

여기까지하면 Python 내에서 사이트 소스를 문자열로 얻을 수 있다.

 

 

위에서 설명한것처럼 req에서 화면의 소스 전체를 얻을 수 있고,

이를 필터링하는 작업만 남았다.

req.text를 출력하면 위와같이 소스 전체가 출력될 것이다.

BeautifulSoup를 통해 필터링해보자.

 

우선 우리는 item 태그 내의 데이터만 필요하다.

(그 안에 title과 description을 가져와야 하니까)

 

그래서 1차적으로 item만 남도록 필터링하면 된다.

이때 사용할 수 있는 메소드가 무엇인지, BeautifulSoup의 공식문서에서 찾아보자.

www.crummy.com/software/BeautifulSoup/bs4/doc/

 

Beautiful Soup Documentation — Beautiful Soup 4.9.0 documentation

Non-pretty printing If you just want a string, with no fancy formatting, you can call str() on a BeautifulSoup object (unicode() in Python 2), or on a Tag within it: str(soup) # ' I linked to example.com ' str(soup.a) # ' I linked to example.com ' The str(

www.crummy.com

 

 

 

find_all() 메소드는 태그나 기타 표현식에 매치되는 데이터를 남기고 필터링해준다.

data.find_all('item')를 하면

item만 필터링해준다.

 

하지만 item 내에서도 한번 더 데이터를 필터링해야한다.

 

대충 아래와 같이 필터링 되었을 것이다.

 

        <item>
            <title><![CDATA[Covid-19: Row over rules 'not just about Greater Manchester']]></title>
            <description><![CDATA[The region's mayor calls on Boris Johnson to negotiate with him over stricter Covid-19 curbs.]]></description>
            <link>https://www.bbc.co.uk/news/uk-54589480</link>
            <guid isPermaLink="true">https://www.bbc.co.uk/news/uk-54589480</guid>
            <pubDate>Sun, 18 Oct 2020 09:20:52 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[London Bridge attack: Steven Gallant up for early release after confronting knifeman]]></title>
            <description><![CDATA[Convicted murderer Steven Gallant was on day release when he helped stop a knifeman's rampage.]]></description>
            <link>https://www.bbc.co.uk/news/uk-54588407</link>
            <guid isPermaLink="true">https://www.bbc.co.uk/news/uk-54588407</guid>
            <pubDate>Sun, 18 Oct 2020 04:20:53 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Brexit: Ball in EU's court over trade deal, says Gove]]></title>
            <description><![CDATA[The EU's behaviour suggests it is not "serious" about a post-Brexit trade deal, Michael Gove says.]]></description>
            <link>https://www.bbc.co.uk/news/uk-politics-54589655</link>
            <guid isPermaLink="true">https://www.bbc.co.uk/news/uk-politics-54589655</guid>
            <pubDate>Sun, 18 Oct 2020 08:56:49 GMT</pubDate>
        </item>

이 중에서 우리는 title과 description의 실질적인 text가 필요하다.

여기서 한차례 더 필터링하면 이렇게 될 것이다.

item.find_all(["title","description"]) ->

 

	<title><![CDATA[Covid-19: Row over rules 'not just about Greater Manchester']]></title>
	<description><![CDATA[The region's mayor calls on Boris Johnson to negotiate with him over stricter Covid-19 curbs.]]></description>

	<title><![CDATA[London Bridge attack: Steven Gallant up for early release after confronting knifeman]]></title>
	<description><![CDATA[Convicted murderer Steven Gallant was on day release when he helped stop a knifeman's rampage.]]></description>

	<title><![CDATA[Brexit: Ball in EU's court over trade deal, says Gove]]></title>
	<description><![CDATA[The EU's behaviour suggests it is not "serious" about a post-Brexit trade deal, Michael Gove says.]]></description>

 

태그 내의 text만 가져올 방법이 없을까?

 

답은 '있다' 이다.

 

 

get_text() 메서드를 사용하면 태그 내의 문자만 빼올 수 있다.

 

 

이제 코딩을 해보자.

 

1. item태그로 필터링

2. item을 순회하면서 title과 description을 빼옴

3. 2번의 데이터를 배열에 저장

 

 

## HTML 소스 가져오기
html = req.text

# print(html)

## 쉬운 구문분석을 위한 BeautifulSoup 호출
bs = BeautifulSoup ( html , "html.parser" ) # BeautifulSoup 객체 생성
fStr = []
cnt = 0
for item in bs.find_all('item'): #item과 그 내의 자식요소만 필터링
    fStr.append([])
    fStr[cnt].append(item.find_all(["title","description"])[0].get_text()) #item 내의 title과 description만 필터링하여 0번째 데이터의 text만 필터링
    fStr[cnt].append(item.find_all(["title","description"])[1].get_text()) #item 내의 title과 description만 필터링하여 1번째 데이터의 text만 필터링
    cnt+=1
print(fStr)

 

 

출력 결과 아래와 같은 배열이 출력된다.

 

 

이 데이터를 노드에서 가져오는것은 다음에 해보도록 하겠다.

728x90