최근 두 수의 최대공약수를 어떻게 동적변수를 사용하여 list로 반환하는지에 관련한 문의가 들어와
꽤 오랜시간 고민해 보았다.
고민한 결과를 아래의 코드로 정리하여 공유하도록 하겠다.
동적변수로는 전역변수인 global()을 사용하였다.
하지만 보통 동적변수를 이용하여 변수를 생성하면 변수 관리가 힘들다는 단점이 있다.
변수가 자동생성되기 때문에 구문에 따라 어떤 변수가 어떤 값을 가지고 있는지 잘 기억해 두어야 한다.
그래서 각 상황에 맞게 적절히 사용하길 바란다.
코드
# 두 수를 입력받는 리스트 생성하기
input_list = list(map(int,input().split()))
#for 문을 사용하여 입력 받은 두 수 각각의 약수 구하기
for i in input_list:
a = globals()['num_{}'.format(i)]= list()
for j in range(i+1):
try:
if i/j - i//j ==0:
a.append(j)
except:
pass
print('num_{}: '.format(i),a) #입력받은 약수 출력
###실제 변수 리스트는 num_입력숫자1, num_입력숫자2 라는 이름의 변수에 저장되어있음을 유의##
신청한 네이버 OPEN API 아이디와 시크릿 코드를 이용하여 블로그 게시물을 불러와줍니다.
저는 검색어를 '상암 맛집' 으로, 출력 개수는 예시로 10개 받아와 보도록 하겠습니다.
import urllib.request
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# 정보입력
client_id = "*********************" # 발급받은 id 입력
client_secret = "*************" # 발급받은 secret 입력
quote = input("검색어를 입력해주세요.: ") #검색어 입력받기
encText = urllib.parse.quote(quote)
display_num = input("검색 출력결과 갯수를 적어주세요.(최대100, 숫자만 입력): ") #출력할 갯수 입력받기
url = "https://openapi.naver.com/v1/search/blog?query=" + encText +"&display="+display_num# json 결과
# url = "https://openapi.naver.com/v1/search/blog.xml?query=" + encText # xml 결과
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
rescode = response.getcode()
if(rescode==200):
response_body = response.read()
#print(response_body.decode('utf-8'))
else:
print("Error Code:" + rescode)
body = response_body.decode('utf-8')
body
[결과]
위를 보면 블로그 게시물에 대한 정보들이 나와 있습니다.
여기서 title (제목) 과 link(블로그 게시물 링크)를 추출하도록 하겠습니다.
여기서는 블로그의 대략적인 내용만 나오지 블로그 내용 전체가 나오지 않으므로
블로그 링크를 추출하여 각각의 블로그로 들어가 내용을 크롤링 하도록 해야 합니다.
step2. 게시글 제목 및 링크만 추출하기
위의 결괏값을 보면 "(큰따옴표)가 글 앞뒤로 붙어있는데 replace를 활용하여 지워주겠습니다.
# 불필요한 ""(큰따옴표)지워주기
body = body.replace('"','')
그다음 "가 없어진 내용을 가지고 제목, 링크를 추출해 보도록 하겠습니다.
그 전에 네이버 블로그 링크만 받아오기 위해 body를 split으로 나누어 글 하나 당 리스트 요소 1개가 되도록 나누도록 하겠습니다. 그 후 list comprehension을 사용하여 naver가 들어간 글만 리스트에 남아있도록 해 줍니다.
#body를 나누기
list1 = body.split('\n\t\t{\n\t\t\t')
#naver블로그 글만 가져오기
list1 = [i for i in list1 if 'naver' in i]
list1
[결과]
이렇게 naver블로그 글만 리스트에 담겼습니다.
제목은 title: 뒤, link: 앞에 위치하므로 re를 사용하여 제목만 추출하도록 하겠습니다.
각 링크는link:뒤,description앞에 위치하여 있습니다.
for문을 이용하여 한번에 제목, 링크를 추출해 보도록 하겠습니다.
#블로그 제목, 링크 뽑기
import re
titles = []
links = []
for i in list1:
title = re.findall('"title":"(.*?)",\n\t\t\t"link"',i)
link = re.findall('"link":"(.*?)",\n\t\t\t"description"',i)
titles.append(title)
links.append(link)
titles = [r for i in titles for r in i]
links = [r for i in links for r in i]
print('<<제목 모음>>')
print(titles)
print('총 제목 수: ',len(titles),'개')#제목갯수확인
print('\n<<링크 모음>>')
print(links)
print('총 링크 수: ',len(links),'개')#링크갯수확인
[결과]
위의 링크를 확인해 보면 보통의 url과는 다르게 '\\'(역 슬래쉬)가 많이 들어가 있음을 확인할 수 있습니다.
보통의 url링크를 가져오기 위해 링크 사이의 '\\'를 지워 다듬어진 링크를 가져오도록 하겠습니다.
# 링크를 다듬기 (필요없는 부분 제거 및 수정)
blog_links = []
for i in links:
a = i.replace('\\','')
b = a.replace('?Redirect=Log&logNo=','/')
# 다른 사이트 url이 남아있을 것을 대비해 한번 더 네이버만 남을 수 있게 걸러준다.
if 'naver.blog.com' in b:
blog_links.append(b)
print(blog_links)
print('생성된 링크 갯수:',len(blog_links),'개')
[결과]
그러면 위와 같이 링크가 예쁘게 출력되었습니다.
step3. 게시글 본문 가져오기
다음으로 블로그 링크를 활용하여 각 페이지에 접근해 본문을 가져오도록 하겠습니다.
NAVER 블로그는 각 게시물에 들어가 보면 아래와 같이 iframe 안에 게시물 본문 글이 위치하여 있습니다.
<body>밑에 iframe이 위치되어있음을 확인가능합니다.
때문에 Selenium을 이용하여 각 게시물의 iframe 접근 후 내용을 추출하는 방식으로 크롤링을 하여야 합니다.
#본문 크롤링
import time
from selenium import webdriver
# 크롬 드라이버 설치
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)
그다음으로 아까 다듬어진 링크에 접속하여 iframe 접근 후 본문의 내용을 크롤링하겠습니다.
본문은 iframe 안의 <div> class='se-main-container' 여기에 아래의 사진과 같이 들어있음을 확인할 수 있습니다.
파란색 부분이 있는 class 이름이 se-main-container임을 확인할 수 있습니다.
그래서 find_element_by_css_selector를 사용해 본문 글을 가져와 contents 변수에 담아 줍니다.
그리고 가끔 예전 블로그 글을 가져오게 될 때가 있는데 그 때는 현재 블로그 html 구조와 달라
No Such Element Error가 날 수 있습니다. 이때 구 블로그 글도 잘 가져올 수 있도록
예외처리를 하여 내용 부분을 크롤링 해 줍니다.
#블로그 링크 하나씩 불러서 크롤링
contents = []
for i in blog_links:
#블로그 링크 하나씩 불러오기
driver.get(i)
time.sleep(1)
#블로그 안 본문이 있는 iframe에 접근하기
driver.switch_to.frame("mainFrame")
#본문 내용 크롤링하기
#본문 내용 크롤링하기
try:
a = driver.find_element(By.CSS_SELECTOR,'div.se-main-container').text
contents.append(a)
# NoSuchElement 오류시 예외처리(구버전 블로그에 적용)
except NoSuchElementException:
a = driver.find_element(By.CSS_SELECTOR,'div#content-area').text
contents.append(a)
#print(본문: \n', a)
driver.quit() #창닫기
print("<<본문 크롤링이 완료되었습니다.>>")
[결과]
그러면 이렇게 모든 게시물의 본문 내용을 가져올 수 있습니다 ;-ㅇ
step4. 크롤링 내용들 DataFrame으로 만들기
마지막으로 아까 추출한 제목, 블로그 링크, 내용을 DataFrame으로 만들어 깔끔하게 정리해 보도록 하겠습니다.
#제목, 블로그링크, 본문내용 Dataframe으로 만들기
import pandas as pd
df = pd.DataFrame({'제목':titles, '링크':blog_links,'내용':contents})
#df 저장
df.to_csv('{}_블로그.csv'.format(quote),encoding='utf-8-sig',index=False)
[결과]
깔끔하게 df로 만들어졌습니다. ㅎㅎ
전체 코드
import urllib.request
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
#정보입력
client_id = "********************" # 발급받은 id 입력
client_secret = "**********" # 발급받은 secret 입력
quote = input("검색어를 입력해주세요.: ") #검색어 입력받기
encText = urllib.parse.quote(quote)
display_num = input("검색 출력결과 갯수를 적어주세요.(최대100, 숫자만 입력): ") #출력할 갯수 입력받기
url = "https://openapi.naver.com/v1/search/blog?query=" + encText +"&display="+display_num# json 결과
# url = "https://openapi.naver.com/v1/search/blog.xml?query=" + encText # xml 결과
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
rescode = response.getcode()
if(rescode==200):
response_body = response.read()
#print(response_body.decode('utf-8'))
else:
print("Error Code:" + rescode)
body = response_body.decode('utf-8')
print(body)
#body를 나누기
list1 = body.split('\n\t\t{\n\t\t\t')
#naver블로그 글만 가져오기
list1 = [i for i in list1 if 'naver' in i]
print(list1)
#블로그 제목, 링크 뽑기
import re
titles = []
links = []
for i in list1:
title = re.findall('"title":"(.*?)",\n\t\t\t"link"',i)
link = re.findall('"link":"(.*?)",\n\t\t\t"description"',i)
titles.append(title)
links.append(link)
titles = [r for i in titles for r in i]
links = [r for i in links for r in i]
print('<<제목 모음>>')
print(titles)
print('총 제목 수: ',len(titles),'개')#제목갯수확인
print('\n<<링크 모음>>')
print(links)
print('총 링크 수: ',len(links),'개')#링크갯수확인
# 링크를 다듬기 (필요없는 부분 제거 및 수정)
blog_links = []
for i in links:
a = i.replace('\\','')
b = a.replace('?Redirect=Log&logNo=','/')
# 다른 사이트 url이 남아있을 것을 대비해 한번 더 네이버만 남을 수 있게 걸러준다.
if 'naver.blog.com' in b:
blog_links.append(b)
print(blog_links)
print('생성된 링크 갯수:',len(blog_links),'개')
#본문 크롤링
import time
from selenium import webdriver
# 크롬 드라이버 설치
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)
#블로그 링크 하나씩 불러서 크롤링
contents = []
for i in blog_links:
#블로그 링크 하나씩 불러오기
driver.get(i)
time.sleep(1)
#블로그 안 본문이 있는 iframe에 접근하기
driver.switch_to.frame("mainFrame")
#본문 내용 크롤링하기
try:
a = driver.find_element(By.CSS_SELECTOR,'div.se-main-container').text
contents.append(a)
# NoSuchElement 오류시 예외처리(구버전 블로그에 적용)
except NoSuchElementException:
a = driver.find_element(By.CSS_SELECTOR,'div#content-area').text
contents.append(a)
#print(본문: \n', a)
driver.quit() #창닫기
print("<<본문 크롤링이 완료되었습니다.>>")
#제목 및 본문 txt에 저장
total_contents = titles + contents
text = open("blog_text.txt",'w',encoding='utf-8')
for i in total_contents:
text.write(i)
text.close()
#제목, 블로그링크, 본문내용 Dataframe으로 만들기
import pandas as pd
df = pd.DataFrame({'제목':titles, '링크':blog_links,'내용':contents})
print(df)
#df 저장
df.to_csv('{}_블로그.csv'.format(quote),encoding='utf-8-sig',index=False)
마무리
오늘은 네이버 API를 이용하여 블로그 제목 및 내용을 가져와봤는데요
이번 포스팅을 작성하는 데 꽤 시간이 걸렸는데 이렇게 깔끔하게 할 수 있어 정말 기쁘네요.ㅎㅎ
API(Application Programming Interface)는 응용 프로그램에서 데이터를 주고받기 위한 방법을 말한다. 어떤 사이트에서 필요한 데이터를 공유받고 싶을 때 어떻게 정보를 요청(request)할지, 그리고 제공받을 수 있는 데이터의 정보에 대한 규격들을 통틀어 API라고 한다. 또한 이러한 내용을 문서화한 것을 'API 규격서'라고 한다.
공공데이터 포털 API 이용하기 step1. 공공데이터 포털 사이트 접속 및 로그인/회원가입
원래 파이썬에서는 함수를 만들 때 def를 사용하여 반복되는 작업을 정의된 함수로 처리한다.
이 때 lambda는 파이썬에서 함수를 더 간결하게 표현하기 위해 만들어진 함수이다.
일반함수→ def(매개변수,매개변수): 프로그램 return 반환값
lambda→ lambda 매개변수: 표현식(return반환값)
예를 들어 두 인수를 더하는 값을 반환값으로 가지는 함수를 각각 만들어 본다고 하자.
#def를 이용하여 두 인수를 더하는 함수 만들기
def add(num1,num2):
return num1 + num2
add(3,4)
결괏값: 7
#lambda를 이용하여 두 인수를 더하는 함수 만들기
add = lambda num1, num2: num1 + num2
add(2,4)
결괏값: 6
이렇게 lambda는 def를 쓸 때보다 간단하게 함수를 만들 수 있다.
map
map은 여러 개의 데이터를 한 번에 return 반환을 할 때 사용하면 편리하다.
때문에 리스트형이나 튜플등의 자료구조에서 사용하기 용이하다.
map을 이용하여 7,8,9에 각각 7을 곱하여 출력하는 lambda를 만들어 보도록 하자.
#map을 이용하여 lambda 만들기
multiple_nums = [7,8,9]
multiple_result = list(map(lambda x: x*7,multiple_nums))
multiple_result
결괏값: [49,56,63]
filter
필터는 말 그대로 특정 조건을 가지고 필터를 한 결과를 보여준다.
예제로 list 값(1~30) 중에서 3의 배수만 결과를 보여주는 lambda를 만들어 보자.
#list comprehension을 이용해 1~30을 가지는 list 만들기
list1 = [i+1 for i in range(30)]
#filter를 사용하여 list1에서 3의 배수만 추출하기
list2 = list(filter(lambda i: i%3 == 0,list1))
list2
결괏값: [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
reduce
reduce는 계산기의 memory(M) 기능과 같이 함수를 만들어 앞의 결과를 누적한 뒤 결과값을 반환하는 함수이다.
예를 들어 입력된 수를 다 더해주는 add함수를 만든다고 해 보자. def와 lambda를 이용한 2가지 방법을 사용하겠다.
#def로 add만들기
def add(*a):
total = 0 #더한 결과값을 저장하는 변수
for i in a:
total +=i #total = total + i와 같음
return total
add(1,2,3,4)
결괏값: 10
#reduce로 add 계산하기
from functools import reduce #python3부터는 reduce를 import해야 사용이 가능
a = [1,2,3,4]
add = reduce(lambda x,y: x+y, a)
add
물론 list = [1,2,3,4,5,6,7,8,9,10] 이렇게 표현을 해도 되지만 더 큰 숫자를 쓸 때를 대비하여
아래와 같이 표현해 보도록 하겠다.
#배열의 사이즈를 지정하는 변수 생성
arr_num = 10
#변수 arr_num의 갯수만큼 배열을 생성하는 리스트 생성
arr = [0]*arr_num
#생성된 arr 확인
print(arr)
- 중간 결괏값: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
#배열의 사이즈를 지정하는 변수 생성
arr_num = 10
#변수 arr_num의 갯수만큼 배열을 생성하는 리스트 생성
arr = [0]*arr_num
#생성된 arr 확인
print(arr)
print("-------------------------------")
#1~10의 배열을 가지는 arr값으로 변환하기
for i in range(arr_num):
arr[i] = i+1
arr
- 최종 결괏값: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
이렇게 1~10까지 값을 차례로 가지는 리스트를 만드는 것에도 3줄 이상의 코드가 작성이 된다.
이때 List comprehension을 사용하면 리스트 만드는 줄의 코드를 1줄로 줄일 수 있다.
List comprehension의 기본 규칙: [(변수에 적용할 수식) for (변수) in (for문이 돌아가는 범위)]
#배열의 사이즈를 지정하는 변수 arr_num 생성
arr_num = 10
"""
list comprehension을 사용하여
변수 arr_num의 갯수만큼 배열을 생성하는 리스트 생성
"""
arr = [i+1 for i in range(arr_num)]
arr
-결괏값:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
위와 같이 List comprehension을 쓰면
arr = [i+1 for i in range(arr_num)] 이 한 줄의 코드만으로 리스트 생성이 가능하다.
조건문을 사용하여 arr 만들기
List comprehension을 사용할 때 조건문(if)을 추가하여 리스트 생성이 가능하다.
예를 들어 1~30의 범위 안에서 홀수의 배열을 가지는 list를 만들어 보자.
조건문 arr : [(변수에 적용할 수식) for (변수) in (for문이 돌아가는 범위) if (조건문)]
# 1~30 중에 홀수(2로 나눴을 때 나머지가 1인 수) arr만들기
arr = [i for i in range(1,30) if i % 2 == 1]
arr