코딩 개발일지

팀프로젝트 시작 (크롤링 / selenium / python to json to db) 본문

AI 본 교육/AI 11주차

팀프로젝트 시작 (크롤링 / selenium / python to json to db)

호기호 2023. 10. 24. 10:58

기획

1. 매일 12시에 [네이버뉴스-TV연예-랭킹] 부분의 TOP10을 가져온다.

2. 크롤링해서 가져오는 과정에서 머신러닝(Chat GPT)을 활용해서 기사내용을 요약한다.

3. 해당 기사를 데이터베이스에 저장한다.

4. 로그인 해서 댓글을 자유롭게 달 수 있다. 좋아요 / 북마크 가능. 닉네임 옆에 MBTI가 표기되서 보는 재미가 있다.

 

크롤링할 페이지 : https://entertain.naver.com/ranking


와이어프레임


ERD


API 설계


프로젝트 시작 후 어려움을 겪은 부분

크롤링을 처음하다보니, 한발 한발 나아가는게 힘들었다...

selenium을 이용해서 크롤링을 하는게 가장 보푠적이고, 좋기도 하고, 편한 방법이다.

 

1. pip install selenium

2. 프로젝트 파일 안에 parser.py 생성 후 크롤링 하는 코드를 여기에 작성할 것이다 !!!

3. models.py 에 크롤링한 데이터를 넣어줄 모델을 만들고 migrate한다.

class Article(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    image = models.URLField(blank=True, null=True)

    def __str__(self):
        return str(self.title)

4. 이제 parser.py 작성 ㄱㄱ

driver = webdriver.Chrome()

이 코드를 작성해야 크롬 드라이버를 사용할 수 있다.

옛날에는 괄호안에 크롬드라이버 경로를 설정해줘야 했었다.

이제 빈 괄호로 두면 알아서 설치되고, 설정해준다고 한다(? 확실하진 않음 ?) 옛날 글들을 구글링 하다보면 헷갈려~~

 

참고로, 코드 완성 후 나중에 터미널에서 실행할 때, 오류가 발생한다.

USB: usb_service_win.cc:415 Could not read device interface GUIDs: 지정된 파일을 찾을 수 없습니다. (0x2)

이 오류는 사실 신경 안써도 되는 오류이지만, 거슬린다면 driver = webdriver.Chrome() 를 바꿔주면 해결됌!!!

options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)

이렇게 바꾸면 해결 완료!!


이제 크롤링할 페이지를 가져온다.

그다음 1위부터 10위까지의 기사를 가져올 것이기때문에, for문을 돌려주면 된다.

먼저 크롤링할 페이지의 elements 창을 보자. 먼저 tit_area의 a태그를 클릭해주도록 만들어서 해당 기사를 들어가야겠다.

for i in range(10):
    articles = driver.find_elements(By.CLASS_NAME, "tit_area")
    article_link = articles[i].find_element(By.TAG_NAME, "a")
    article_link.click()

같은 방법으로 해당기사로 들어갔을 때의 elements 창을 보면서 title, contentm image를 가져오면 되겠다는 생각이 든다.

for i in range(10):
    articles = driver.find_elements(By.CLASS_NAME, "tit_area")
    article_link = articles[i].find_element(By.TAG_NAME, "a")
    article_link.click()

    title_element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "end_tit")))
    title = title_element.text

    content_element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "end_body_wrp")))
    content = content_element.text

    image_element = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
        (By.XPATH, '//div[@class="end_body_wrp"]//img[1]')))
    image_url = image_element.get_attribute("src")

WebDriverWait 를 써서 class name으로 title, content 를 가져왔고, image는 XPATH를 이용해서 가져왔다.

XPATH를 작성하는 양식은 옛날 글에서는 코드 작성이 조금 다를것이다. 2023.10.24 기준으로는 이렇게 쓰시길~~


이제 크롤링한 데이터베이스에 저장하고 싶었는데, 몇시간동안 노력해봐도 안됐다.......ㅠㅠ

from articles.models import Article

이런식으로 import를 해주면, 자꾸 articles를 읽어오지 못했다.

이유를 나중에 생각해보니, paser.py를 터미널로 실행시킨다는게 장고를 runserver로 돌리는게 아니라, 별도의 파이썬 파일을 실행시키는 행위이기 때문에, import를 못받아오느 것이었다. parser.py의 위치를 이곳저곳 옮겨봤지만, 실패,,,,,

 

결국 json파일로 저장시킨 후, json파일을 데이터베이스에 넣는 방식을 선택하게 됐다.

(이후 장고크론(?)인가 뭔가를 쓰면 더 쉽게 크롤링 할 수 있다는 말을 어디선가 들었다.)

file_path = "./news.json"
data['articles'].append({
        "title": title,
        "content": content,
        "image": image_url
    })
with open(file_path, 'w', encoding="UTF-8") as outfile:
    json.dump(data, outfile, indent=2, ensure_ascii=False)

이렇게 하면 news.json파일을 자동으로 생성해서 넣어준다.

꿀팁) encoding 방식을 넣어주고, ensure_ascii=False 를 해주면, 한글이 깨지지 않고 json파일에 잘 들어간다.

꿀팁2) indent=값 을 넣어주면, 들여쓰기가 되서 json파일 볼 때, 보기 편하다.

<최종 코드>

file_path = "./news.json"

options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)

# driver = webdriver.Chrome()


data = {}
for i in range(2):
    articles = driver.find_elements(By.CLASS_NAME, "tit_area")
    article_link = articles[i].find_element(By.TAG_NAME, "a")
    article_link.click()

    title_element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "end_tit")))
    title = title_element.text

    content_element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "end_body_wrp")))
    content = content_element.text

    image_element = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
        (By.XPATH, '//div[@class="end_body_wrp"]//img[1]')))
    image_url = image_element.get_attribute("src")

    if 'articles' not in data:
        data['articles'] = []

    data['articles'].append({
        "title": title,
        "content": content,
        "image": image_url
    })

    driver.execute_script("window.history.go(-1)")
    time.sleep(2)

with open(file_path, 'w', encoding="UTF-8") as outfile:
    json.dump(data, outfile, indent=2, ensure_ascii=False)

driver.quit()


# JSON 데이터를 불러옴
with open(file_path, "r", encoding='utf-8') as json_file:
    json_data = json.load(json_file)

이제 데이터베이스에 넣어주면 된다.

import sqlite3
# SQLite 데이터베이스에 연결
conn = sqlite3.connect('db.sqlite3')
cursor = conn.cursor()

# 데이터베이스에 데이터 입력
for article in data['articles']:
    title = article['title']
    content = article['content']
    image = article['image']
    cursor.execute(
        "INSERT INTO articles_article (title, content, image) VALUES (?, ?, ?)", (title, content, image))

# 데이터베이스에 변경 내용 저장
conn.commit()

# 데이터베이스 연결 닫기
conn.close()

for문 안에서 데이터베이스에 넣는 코드가 좀 복잡하고, 이해가 안됐다...

execute를 이용하고, ? 안에 값을 넣어주는 코드인데, 구글링으로 코드를 찾아서 입력했더니 데이터베이스에 잘 들어갔다.

<news.json>

{
  "articles": [
    {
      "title": "[팝업★]전소민, 불나방 이제 안녕‥개리→이광수, 아쉬운 '런닝맨' 하차 ★들",
      "content": "이미지 원본보기\n개리, 전소민, 이광수/사진=Mnet 제공, 민선유기자\n\n\n[헤럴드POP=김나율기자]개리부터 전소민까지, '런닝맨'을 빛냈던 멤버들이 줄지어 하차했다. 전소민이 6년 만에 하차하게 되면서 불나방은 볼 수 없게 됐다.\n\n오는 30일, 전소민은 SBS '런닝맨' 마지막 녹화를 한다.  (뒤에생략^^;;)
    },
    {
      "title": "'7살 연하♥' 손헌수, 결혼식 사진 대방출..박수홍 부부도 축복",
      "content": "[스타뉴스 | 최혜진 기자]\n이미지 원본보기\n손헌수 결혼 /사진=손헌수\n이미지 원본보기\n박수홍 아내 김다예(왼쪽부터), 이경실, 손헌수 아내, 이홍렬, 손헌수, 박수홍 /사진=손헌수\n이미지 원본보기\n박수홍 아내 김다예(왼쪽부터), 박수홍, 손헌수 아내, 조혜련, 손헌수/사진=손헌수\n개그맨 손헌수가 결혼식 사진을 대방출했다.\n\n손헌수는 23일 \"경황이 없어 인사가 늦었다. 감사한 분들이 너무 많아 우선 이곳에서 감사하다는 인사를 드리고, 한국으로 돌아가면 따로 (다시) 인사드리겠다\"고 밝혔다. (뒤에생략^^;;)
    }
  ]
}

<db table>에 잘 들어간 모습~