Crawling 최종

문정현·2024년 8월 24일
0

디렉토리를 탐색할 방법까지 찾아서 드디어 크롤러를 완성했다

해답은 바로 알고리즘에 있었다.

DFS 깊이 탐색 알고리즘

def dfs_crawl(driver, visited, crawledPages, json_data):
    while True:
        soup = crawl_and_parse(driver)
        seperator_div = soup.find('div', class_='v-subheader')

        if not seperator_div:
            print("No 'v-subheader' class found.")
            break

        siblings = seperator_div.find_next_siblings('div')

        try:
            current_dir = seperator_div.find_previous_sibling('div').getText()
            prev_div = seperator_div.find_previous_sibling('div').find_previous_sibling()
            if prev_div.getText() == "sidebar.root" or prev_div.getText() == "루트":
                print("This is root directory")
                break
        except AttributeError:
            break

        unvisited_sibling = None

        if current_dir not in crawledPages:
            crawledPages.append(current_dir)
            # 크롤링 수행
            links = extract_links(driver)
            for link in links:
                print(link)
                driver.get(link)
                # time.sleep(1)
                contents_html = crawl_html_by_class(driver, "v-main__wrap")
                jsonData = extract_doc(contents_html)
                print(json.dumps(jsonData, ensure_ascii=False, sort_keys=True, indent=4))
                json_data.append(jsonData)

        for sibling in siblings:
            sibling_text = sibling.get_text().strip()
            if sibling_text not in visited:
                unvisited_sibling = sibling_text
                break

        if unvisited_sibling:
            visited.add(unvisited_sibling)
            path_pointer(driver, unvisited_sibling)
            dfs_crawl(driver, visited, crawledPages, json_data)
        else:
            path_pointer(driver, prev_div.get_text().strip())
            time.sleep(1)
            break
    return json_data

코드가 좀 많이 길지만 밑으로 내려가 하나씩 뜯어본다

soup = crawl_and_parse_menu(driver) # (1)
seperator_div = soup.find('div', class_='v-subheader') # (2)

if not seperator_div:
    print("No 'v-subheader' class found.")
    break

siblings = seperator_div.find_next_siblings('div') # (3)

def crawl_and_parse_menu(driver):
    contents_html = crawl_html_by_class(driver, "__view")
    return BeautifulSoup(contents_html, 'html.parser')

(1) __view class의 div 즉 메뉴바에서 HTML데이터를 긁어서 bs4로 파싱
(2) v-subheader는 메뉴바에서 디렉토리 이동과 현재 디렉토리 파일들의 path를 구분하고 있는 div
(3) seperator div 기준 아래는 현 디렉토리의 모든 파일 여기에는 파일 뿐만 아니라 이동 경로 또한 포함하고 있음

try:
    current_dir = seperator_div.find_previous_sibling('div').getText() # (1)
    prev_div = seperator_div.find_previous_sibling('div').find_previous_sibling() # (2)
    if prev_div.getText() == "sidebar.root" or prev_div.getText() == "루트":
        print("This is root directory")
        break
except AttributeError:
    break

(1) 현재 크롤링을 수행하고자 하는 dir
(2) 현재 디렉토리를 기준으로 이전 디렉토리

def extract_links(driver): # (1)
    html_content = crawl_html_by_class(driver, 'v-navigation-drawer__content')

    soup = BeautifulSoup(html_content, 'html.parser')
    links = []
    for a_tag in soup.find_all('a', class_='v-list-item'):
        href = a_tag.get('href')
        full_url = urljoin("https://wiki.direa.synology.me", href)
        if full_url not in links:
            links.append(full_url)
    return links
unvisited_sibling = None # (2) 
if current_dir not in crawledPages:
    links = extract_links(driver)
    for link in links: # (3) 
        driver.get(link)
        contents_html = crawl_html_by_class(driver, "v-main__wrap")
        jsonData = extract_doc(contents_html)
        json_data.append(jsonData)
    crawledPages.append(current_dir) # (4)

(1) 메뉴바에서 모든 파일의 링크 src를 추출하는 함수
여기서 파일을 담고 있는 div의 class는 v-list-item이다
(2) 깊이 탐색을 위한 미방문 노드 리스트
(3) 문서를 순회하며 데이터 크롤링
(4) 크롤링 이후 방문 노드로 append

”크롤링 목적의 깊이탐색”

def path_pointer(driver, text):
    driver.find_element(By.XPATH, f"//div[@class='v-list-item__title'][contains(text(), '{text}')]").click()
    time.sleep(1)
for sibling in siblings:
    sibling_text = sibling.get_text().strip()
    if sibling_text not in visited: # (1)
        unvisited_sibling = sibling_text
        break
if unvisited_sibling:
    visited.add(unvisited_sibling)
    path_pointer(driver, unvisited_sibling)  # (2)
    dfs_crawl(driver, visited, crawledPages, json_data)  # (3)
else:
    path_pointer(driver, prev_div.get_text().strip())  # (4)
    break

(1) 방문하지 않은 노드면 unvisited_sibling에 선언
(2) 현재 pointer가 바라보는 디렉토리가 방문하지 않은 노드면 해당 디렉토리로 이동
(3) 재귀적 호출
(4) 주석 (2)번 조건이 아니라면 이전 디렉토리로 이동

”디렉토리 이동 목적의 깊이탐색”

profile
주니어 개발자를 꿈꾸며

0개의 댓글