Post

[DEV] Web Crawling

[DEV] Web Crawling

크롤링 가능 여부 확인

  • [사이트의 root url]/robots.txt 에서 확인!

카카오의 경우

1
2
3
4
5
6
7
User-agent: *
Disallow: /wp-admin/
Allow: /wp-admin/admin-ajax.php

Sitemap: https://tech.kakao.com/wp-sitemap.xml

Disallow: /wp-content/uploads/wpo-plugins-tables-list.json

원칙적으로 wp-admin 페이지는 크롤링하지 않는 것으로

1. kakao

  • 글 제목, 링크, 날짜는 메인 화면에서 바로 읽을 수 있음
  • 페이지도 url에 들어가기 때문에 가장 쉬웠던 사이트!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
from bs4 import BeautifulSoup
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
import pandas as pd

user_agent = user_agent = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}

kakao = "https://tech.kakao.com/blog/"

kakao_title = []
kakao_link = []
kakao_date = []

# title, date, link
for i in range(1, 36):
    res = requests.get(kakao+"page/{}/#posts".format(i), user_agent)
    soup = BeautifulSoup(res.text, "html.parser")
    soup.find("section", class_="elementor-section elementor-top-section elementor-element elementor-element-2252c9ab elementor-section-boxed elementor-section-height-default elementor-section-height-default").decompose()

    ktitle = soup.find_all("article", "elementor-post")
    for title in ktitle:
        kakao_title.append(title.find("div", "elementor-post__text").find("h3", "elementor-post__title").a.text.strip())
        kakao_link.append(title.find("div", "elementor-post__text").find("h3", "elementor-post__title").a["href"])
        kakao_date.append(title.find("div", "elementor-post__text").find("div", "elementor-post__meta-data").find("span", "elementor-post-date").text.strip())
    time.sleep(1)


  • 태그는 글 속에 있어서 selenium을 이용하여 들어갔다 나왔다 반복했음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tags
kakao_tag_list = []

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

for link in kakao_link:
    kakao_tag = []
    driver.get(link)

    soup = BeautifulSoup(driver.page_source, "html.parser")
    tags = soup.find_all("a", "elementor-post-info__terms-list-item")
    for tag in tags:
        kakao_tag.append(tag.text)
    kakao_tag_list.append(list(kakao_tag))

2. woowahan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from time import sleep
import pickle

import requests
from bs4 import BeautifulSoup

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager

# header            
user_agent = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"}    

data = [] 
company_name = "우아한형제들"

with webdriver.Chrome(service=Service(ChromeDriverManager().install())) as driver:
    # 브라우저 창 최대화
    driver.maximize_window()
    driver.get("https://techblog.woowahan.com/")
    # post의 url 리스트 
    urls = []
    while True:
        sleep(1)
        for post in driver.find_element(By.CLASS_NAME, "posts").find_elements(By.CLASS_NAME, "item")[10:]:
            # post의 url 수집
            urls.append(post.find_element(By.TAG_NAME, "a").get_attribute("href"))
        # 스크롤을 최대로 내림
        prev_height = driver.execute_script("return document.body.scrollHeight")
        while True:
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
            sleep(2)
            curr_height = driver.execute_script("return document.body.scrollHeight")
            if curr_height == prev_height:
                break
            prev_height = curr_height
        # 다음 페이지 버튼 클릭
        button = driver.find_elements(By.CLASS_NAME, "nextpostslink")
        if button:
            ActionChains(driver).click(button[0]).perform()
        else:
            break
        
for url in urls:
    # post별 url 요청
    res = requests.get(url, user_agent)
    soup = BeautifulSoup(res.text, "html.parser")
    
    # title, date, tag
    title = soup.find("h1").text
    pub_date = soup.find("div", "author").find("span").text.strip()
    tags = []
    for tag in soup.find("span","cats").find_all("a", "cat-tag"):
        tags.append(tag.text.strip())

    sleep(0.5)
  • 페이지가 url에 뜨지 않고, post 방식으로 받아서 다음 페이지 버튼을 눌러서 이동해야 했던 사이트!
  • 버튼을 누를 때 창을 아래로 내려야 한다는 것을 몰라서 엄청 헤맸다,,!
  • 이 코드를 다시 보며 생각해보니 태그가 글 안에 있는 다른 사이트들도 그냥 페이지에 들어가서 제목, 날짜, 태그를 한 번에 긁어오는게 나을 것 같다!

3. 이스트소프트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import requests
from bs4 import BeautifulSoup
from time import sleep
import pickle

# header
user_agent = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"}

data = []
company_name = "이스트소프트"

base_url = "https://blog.est.ai/"
for page_url in ([base_url] + [base_url + f"page{i}" for i in range(2, 5)]):
    # 페이지 별 url 요청
    page_res = requests.get(page_url, user_agent)
    page_soup = BeautifulSoup(page_res.text, "html.parser")
    
    for post in page_soup.find_all("li", "post-preview"):
        # 각 post별 url
        url = post.find("article").find("a")["href"]
        # post별 url 요청
        res = requests.get(url, user_agent)
        soup = BeautifulSoup(res.text, "html.parser")
        
        # title, date, tag
        title = soup.find("h1").text
        pub_date = soup.find("span", "post-meta").text

        tags = []
        for tag in soup.find("div","blog-tags").find_all("li"):
            tags.append(tag.find("a").text)
            
  • 페이지도 url에 바로 들어가고, 태그가 div 안에 li 태그로 들어가있어서 뽑아내기 쉬웠음!

4. Devocean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import requests
from bs4 import BeautifulSoup
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
import pandas as pd

user_agent = user_agent = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}

devocean = "https://devocean.sk.com/blog/sub/index.do?ID=&boardType=&searchData=&page={}&subIndex=%EC%B5%9C%EC%8B%A0+%EA%B8%B0%EC%88%A0+%EB%B8%94%EB%A1%9C%EA%B7%B8"

devo_title = []
devo_link = []
devo_date = []

# title, date, link
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

for i in range(1, 62):
    driver.get(devocean.format(i))

    page = driver.find_element(By.XPATH, '//*[@id="wrapper"]/div/section[2]/div/div[2]/div/ul')
    for j in range(1, 11):
        driver.implicitly_wait(5)
        title = page.find_element(By.CSS_SELECTOR, "#wrapper > div > section.sub-sec.blog.cont01 > div > div.sub-sec-aside > div > ul > li:nth-child({}) > div > a > h3".format(j))
        devo_title.append(title.get_attribute("textContent").strip())

        link = page.find_element(By.CSS_SELECTOR, "#wrapper > div > section.sub-sec.blog.cont01 > div > div.sub-sec-aside > div > ul > li:nth-child({}) > div > a".format(j))
        devo_link.append(link.get_attribute("href").strip())

        date = page.find_element(By.CSS_SELECTOR, "#wrapper > div > section.sub-sec.blog.cont01 > div > div.sub-sec-aside > div > ul > li:nth-child({}) > div > div.sec-box > div > div.author-area.pc_view > span.date".format(j))
        devo_date.append(date.get_attribute("textContent").strip())

# tags
devo_tag_list = []

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

for i in range(1, 62):
    driver.get(devocean.format(i))

    for j in range(1, 11):
        devo_tag = []
        title = driver.find_element(By.XPATH, '//*[@id="wrapper"]/div/section[2]/div/div[2]/div/ul/li[{}]/div/div[1]/div/div[1]'.format(j))
        tags = title.find_elements(By.TAG_NAME, "span")

        for tag in tags:
            devo_tag.append(tag.get_attribute("textContent").replace("#", ""))
        devo_tag_list.append(list(devo_tag))

    time.sleep(1)
  • selenium의 find_element로 XPATH와 CSS_SELECTOR를 사용해보니 편해서 사용하긴 했는데 이게 좋은 방법인지는 모르겠다!
  • 목록 페이지에 태그도 같이 나와있어서 어렵지 않았던 사이트

느낀 점

  • 사실 타고타고 들어가는거라 구조를 잘 파악하기만 하면 어디든 적용할 수 있을 것 같다
  • request와 selenium 중 각각 어떤 것을 사용하면 좋을지 판단해야 할 듯
    • find, find_allfind_element 구분!
  • url에 페이지 번호가 안 뜨는 곳은 버튼 등으로 페이지를 이동해야 한다
    • 이 때 페이지 크기와 아래로 스크롤 하는 것 꼭 고려할 것!
  • 크롤링을 세 명이서 사이트를 나누어서 진행했는데, 코드를 모아보니 사람마다 스타일이 다른 것 같아 신기했다
    • 그 중 내 코드가 가장 뭔가 정리되고 구조화되지 않은 것 같아서 이 부분에서 많이 배웠다!
    • 다음부터는 더 코드를 깔끔하고 규칙적으로 작성하려고 노력해봐야겠다

Github

https://github.com/bokyung124/tech_dashboard/tree/main/crawler

This post is licensed under CC BY 4.0 by the author.