import requests
from urllib import request
from bs4 import BeautifulSoup
import time
import pandas as pd
import re

저는 그때그때 import 하기보다 최상단에 import 하는 편을 좋아하기 때문에 일단 사용할 라이브러리들을 불러와줍니다.

requests와 BeautifulSoup4는 크롤링에 있어서 html소스를 가져오고 정제하는 역할을 합니다.

time을 import 한 이유는 크롤링을 너무 빨리 진행하면 봇으로 인식하여 ip 차단을 당하기 때문입니다.

그리고 데이터프레임으로 저장하기 위해 pandas를 이용합니다.

- dc offical app 문자열을 삭제하기 위해 re를 불러옵니다.

df = pd.DataFrame(columns = ['contents','mbti'])

데이터프레임에서 제목과 내용을 뜻하는 contents 그리고 게시판 이름이 들어갈 mbti를 지정해줍니다.

BASE_URL = "https://gall.dcinside.com/mgallery/board/lists"
ARTICLE_BASE_URL = "https://gall.dcinside.com"

# user_agent 리스트 설정
user_agent_list = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
'Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16.2',
'Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.62',
'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.9.2a1pre) Gecko',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.7) Gecko/2009032803',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:39.0) Gecko/20100101 Firefox/75.0'
]

BASE_URL로 마이너갤러리 기본 주소와, 원주소인 ARTICLE_BASE_URL 지정합니다.

user_agent_list 에서 choice하여 사용할 리스트는 봇으로 인식하지 않는데 도움을 줄 브라우저 정보 여러가지를 입력합니다. 

 

User-Agent는 https://www.useragentstring.com/ 를 참고하시면 됩니다.

#몇 페이지부터 몇 페이지까지
for i in range(2, 4):
	# 파라미터 설정
	params = {'id': 'intp_mbti','page':i}

	#user_agent_list로부터 choice 합니다.
	user_agent = random.choice(user_agent_list)
    
	#choice된 User-Agent를 headers로 받습니다.
	headers = {'User-Agent': user_agent}

	response = requests.get(BASE_URL, params=params, headers=headers)
	soup = BeautifulSoup(response.content, 'html.parser')

	#실질적 글 목록 부분
	article_list = soup.find('tbody').find_all('tr',{'data-type':'icon_pic'})
	article_list += soup.find('tbody').find_all('tr',{'data-type':'icon_txt'})

1단계 for에서의 작업입니다.

해당하는 부분에서는 몇 페이지부터 몇 페이지 까지 크롤링 할 것인지 결정할 수 있습니다.

User-Agent는 리스트로부터 choice를 통해 추출합니다.

params에서는 탐색할 갤러리의 / 이후 주소와 page로 i를 받았습니다.

해당하는 부분에서 소스를 불러오고

실제로 유저가 작성한을 불러오기 위해서는 icon_pic 이나 icon_txt를 포함해야 합니다.

(이 부분에 대해서 제외하기 위함입니다.)

 

과거 다른분들이 작성해주셨던 코드로는 사이트도 소스를 자주 바꾸기 때문에 그때마다 개발자 도구로 확인해서 접근해 주어야 합니다. 

	# 한 페이지에 있는 모든 게시물을 긁어오는 코드 
	for tr_item in article_list:

		# 제목 추출
		title_tag = tr_item.find('a', href=True)
		title = title_tag.text

		print("제목: ", title)
        
		#user_agent_list로부터 choice 합니다.
		user_agent = random.choice(user_agent_list)
    
		#choice된 User-Agent를 headers로 받습니다.
		headers = {'User-Agent': user_agent}
        
		# 게시물에 request
		article_response = requests.get(ARTICLE_BASE_URL + title_tag['href'], headers=headers)
		time.sleep(random.random()*2+3)
		print("url: ", article_response.url)

		article_id = (title_tag['href'].split('no=')[1]).split('&')[0]
		print("게시물 ID : ", article_id)

		article_soup = BeautifulSoup(article_response.content, 'html.parser')

2단계 for 안에서의 작업입니다. - 1

  1. article 제목은 href를 가지고 있는 a 태그에 대하여 추출하고
  2. 게시글의 url은 원 주소와 href의 값을 이용합니다.
  3. 해당하는 게시글 주소에 대해 리스트로부터 choice한 User-Agent header을 포함하여 request합니다.
  4. 게시글 번호는 href의 값에서 split을 이용한 String 정제를 이용하여 추출합니다.

마지막으로, request한 게시글 본문을 포함한 소스에 대해 정제요청을 합니다.

		# 게시물 부분의 태그
		article_contents=str(article_soup.find('div', class_='write_div'))
		article_contents=BeautifulSoup(article_contents, "lxml").text
        
		# 제목과 게시글에서 url 제거 
		pattern = 'http[s]?://(?:[a-zA-Z]|[0-9]|[$\-@\.&+:/?=]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
		repl = ''        
		title = re.sub(pattern=pattern, repl=repl, string=title)
		article_contents = re.sub(pattern=pattern, repl=repl, string=article_contents)
        
		# - dc official App 제거 
		article_contents = article_contents.replace('- dc official App', repl)
		print("내용: ", article_contents)
        
		# 제목 + 게시글
		article=title+article_contents
        
		# 내용이 있으면 데이터프레임에 저장
		if article_contents is not None:
			index = len(df) + 1
			df.loc[index] = [article,'intp']
	# 데이터프레임을 csv로 저장
	df.to_csv("intp.csv", index = False, encoding="utf-8-sig")

2단계 for 안에서의 작업입니다. - 2

  1. 게시글에서 본문에 속하는 부분은 div 태그 write_div라는 클래스에 속합니다.
  2. BeautifulSoup를 이용하여 텍스트 부분만을 추출해내어 img 태그가 담긴 부분을 배제합니다.
  3. url regex pattern을 이용하여 외부 링크를 담은 쓸모없는 내용을 제거합니다.
  4. 정보가 될 수 없는 반복되는 -dc official App에 대하여 일괄 제거합니다.
  5. 본문이 존재하지 않는 글은 저장하지 않습니다.
  6. 제목과 본문은 글쓴이의 특성을 담고 있으므로 정보가 될 수 있습니다.
  7. 제목과 본문을 합하고 데이터프레임의 마지막에 계속하여 mbti타입과 제목+본문을 저장합니다.

마지막으로 수집한 데이터가 저장된 데이터프레임을 .csv로 저장하여 데이터셋으로 확보합니다.

 

이 때, 주의해야 할 점은 encoding="utf-8-sig"를 파라미터로 지정하여야 한다는 것입니다.

그렇지 않으면 메모장에서는 열리나 엑셀에서는 안열리게 됩니다.

 

또한, 게시글에 본문이 없는 경우 일단 누락하였으며, 이후 모델링 과정에서 게시글의 길이가 과도하게 짧은 경우를 배제해야 하는지 고려해야합니다.

 

작업 결과물의 모니터링 사진입니다.

모니터링시 표시되는 내용

 

※ 해당 게시글은 아래 링크의 SourceCode를 응용하였습니다. 

[Python] 디씨인사이드 글 / 게시물 / 사진 크롤링 — 치킨과 개발의 상관관계 (tistory.com)

+ Recent posts