강의 15 : 작동중인 셀레늄 크롤링

지난 강의에서는 Selenium의 기본 사용법을 배웠고, 이번 강의에서는 실제 사례를 결합하여 Selenium의 적용 가능한 시나리오와 사용법을 경험해 보겠습니다.

1. 준비

이 강의를 시작하기 전에 다음 준비를 완료했는지 확인하십시오.

  • Chrome 브라우저를 설치하고 ChromeDriver를 올바르게 구성하십시오.
  • Python (최소 버전 3.6)을 설치하고 Python 프로그램을 성공적으로 실행합니다.
  • Selenium 관련 패키지가 설치되고 Selenium을 사용하여 Chrome 브라우저를 열 수 있습니다.

2. 적용 가능한 시나리오

이전의 실제 사례에서 일부 웹 페이지는 요청으로 직접 크롤링 할 수 있고 일부는 Ajax를 분석하여 직접 크롤링 할 수 있습니다. 웹 사이트 유형에 따라 적용 가능한 크롤링 방법이 있습니다.

Selenium에는 적용 가능한 시나리오도 있습니다. 자바 스크립트로 렌더링 된 웹 페이지의 경우 대부분의 경우 요청을 직접 사용하여 웹 페이지의 소스 코드를 크롤링 할 수 없지만 경우에 따라 요청을 직접 사용하여 Ajax 요청을 시뮬레이션하여 데이터를 직접 가져올 수 있습니다.
그러나 어떤 경우에는 Ajax의 일부 요청 인터페이스가 토큰, 서명 등과 같은 일부 암호화 된 매개 변수를 전달할 수 있습니다. 이러한 매개 변수가 생성되는 방식을 분석하지 않으면 이러한 매개 변수를 시뮬레이션하고 구성하기가 어렵습니다. 어떻게하나요? 현재로서는 Selenium을 사용하여 브라우저 렌더링을 구동하여 WYSIWYG 크롤링을 수행하는 다른 방법을 찾을 수 있으므로이 페이지 뒤에서 발생하는 요청, 제공되는 데이터, 페이지 렌더링 방법에 대해 신경 쓸 필요가 없습니다. 우리가 보는 페이지는 브라우저가 Ajax 요청 및 JavaScript 렌더링을 시뮬레이션하는 데 도움이 된 최종 결과이며, Selenium은 목표에 도달하기 위해 Ajax 요청 분석 및 시뮬레이션 단계를 우회하는 것과 동일한 최종 결과를 얻을 수도 있습니다.

그러나 Selenium은 확실히 한계가 있으며 크롤링 효율성이 낮고 일부 크롤링은 브라우저 작동을 시뮬레이션해야하므로 구현하기가 상대적으로 번거 롭습니다. 그러나 일부 시나리오에서는 효과적인 크롤링 방법이기도합니다.

3. 타겟 크롤링

이 강의에서는 Selenium 적용 사이트를 예로 들어 보겠습니다. 링크는 https://dynamic2.scrape.cuiqingcai.com/ 입니다. 이전과 동일한 영화 사이트입니다. 페이지는 그림과 같습니다.

여기에 사진 설명 삽입
언뜻보기에 페이지는 이전 페이지와 다르지 않지만 자세히 살펴보면 Ajax 요청 인터페이스와 각 영화의 URL에 암호화 된 매개 변수가 포함되어 있음을 알 수 있습니다.

예를 들어, 그림과 같이 영화를 클릭하고 URL 변경을 관찰합니다.
여기에 사진 설명 삽입
여기에서 상세 페이지의 URL이 이전과 다른 것을 볼 수 있습니다. 이전의 경우 URL의 상세 바로 뒤에 1, 2, 3 등의 id가오고 있었지만 바로 길어졌습니다. 문자열은 Base64로 인코딩 된 콘텐츠로 보이므로 법에 따라 상세 페이지의 URL을 직접 구성 할 수 없습니다.

자, Ajax 요청을 직접 살펴 보겠습니다. 목록 페이지의 1 페이지부터 10 페이지까지 클릭하여 그림과 같이 Ajax 요청이 어떻게 보이는지 살펴 봅니다.

여기에 사진 설명 삽입
여기에서 인터페이스의 매개 변수에 이전보다 하나 이상의 토큰이 있고 요청 된 토큰이 매번 다른 것을 볼 수 있으며이 토큰은 Base64 인코딩 문자열처럼 보입니다. 더 어려운 점은이 인터페이스가 여전히 시간에 민감하다는 것입니다. Ajax 인터페이스 URL을 직접 복사하면 짧은 시간 내에 액세스 할 수 있지만 일정 시간이 지나면 액세스 할 수 없게되고 401 상태 코드가 직접 반환됩니다.

우리는 지금 무엇을해야합니까? 이전에는 요청을 직접 사용하여 Ajax 요청을 구성 할 수 있었지만 이제 Ajax 요청 인터페이스에이 토큰이 있으며 여전히 가변적입니다. 이제 토큰 생성 로직을 모르기 때문에 크롤링 할 Ajax 요청을 직접 구성 할 수 없습니다. 알았다. 이때 토큰 생성 로직을 분석하고 Ajax 요청을 시뮬레이션 할 수 있지만이 방법은 상대적으로 어렵다. 따라서 여기에서 Selenium을 직접 사용하여이 단계를 우회하고 최종 JavaScript 렌더링 페이지의 소스 코드를 직접 얻은 다음 데이터를 추출 할 수 있습니다.

따라서이 강의에서 달성해야하는 목표는 다음과 같습니다.

  • Selenium을 통해 목록 페이지를 탐색하여 각 영화의 세부 사항 페이지 URL을 가져 오십시오.
  • Selenium을 사용하여 이전 단계에서 얻은 세부 정보 페이지 URL에 따라 각 영화의 세부 정보 페이지를 크롤링합니다.
  • 각 영화의 이름, 카테고리, 악보, 소개, 표지 및 기타 콘텐츠를 추출합니다.

4. 목록 페이지 크롤링

먼저 다음 초기화 작업을 수행해야합니다. 코드는 다음과 같습니다.

from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import logging
logging.basicConfig(level=logging.INFO,
                   format='%(asctime)s - %(levelname)s: %(message)s')
INDEX_URL = 'https://dynamic2.scrape.cuiqingcai.com/page/{page}'
TIME_OUT = 10
TOTAL_PAGE = 10
browser = webdriver.Chrome()
wait = WebDriverWait(browser, TIME_OUT)

먼저 webdriver, WebDriverWait 등 필요한 Selenium 모듈을 가져 왔습니다. 나중에 페이지 크롤링을 구현하고 대기 설정을 지연하는 데 사용할 것입니다. 그런 다음 일부 변수 및 로그 구성이 정의되며 이는 이전 몇 가지 레슨의 내용과 유사합니다. 그런 다음 Chrome 클래스를 사용하여 웹 드라이버 개체를 생성하고이를 브라우저에 할당합니다. 여기서 브라우저를 사용하여 일부 Selenium API를 호출하여 스크린 샷, 클릭, 풀다운 등과 같은 일부 브라우저 작업을 완료 할 수 있습니다. 마지막으로, 페이지 로딩을위한 최대 대기 시간을 구성 할 수있는 WebDriverWait 객체를 선언합니다.

좋습니다. 그러면 목록 페이지를 관찰하고 목록 페이지 크롤링을 구현합니다. 여기에서 목록 페이지의 URL이 여전히 특정 패턴을 가지고 있음을 알 수 있습니다. 예를 들어 첫 페이지는 https://dynamic2.scrape.cuiqingcai.com/page/1 이고 페이지 번호는 URL의 마지막 번호이므로 여기에서 각각을 직접 구성 할 수 있습니다. 페이지의 URL입니다.

그렇다면 각 목록 페이지가 성공적으로로드되었는지 어떻게 판단합니까? 매우 간단합니다. 원하는 콘텐츠가 페이지에 나타나면로드가 성공했음을 의미합니다. 여기에서는 Selenium의 암묵적 판단 조건을 사용하여 판단 할 수 있는데, 예를 들어 그림과 같이 각 영화 정보 블록의 CSS 선택자는 #index .item입니다.
여기에 사진 설명 삽입
따라서 여기에서 visible_of_all_elements_located 판단 조건과 CSS 선택기의 내용을 직접 사용하여 페이지가로드되었는지 여부를 결정합니다. WebDriverWait의 제한 시간 구성을 사용하면 페이지로드 모니터링의 10 초를 달성 할 수 있습니다. 구성된 조건이 10 초 이내에 충족되면 페이지가 성공적으로로드되었음을 의미하고 그렇지 않으면 TimeoutException이 발생합니다.

코드는 다음과 같이 구현됩니다.

def scrape_page(url, condition, locator):
   logging.info('scraping %s', url)
   try:
       browser.get(url)
       wait.until(condition(locator))
   except TimeoutException:
       logging.error('error occurred while scraping %s', url, exc_info=True)
def scrape_index(page):
   url = INDEX_URL.format(page=page)
   scrape_page(url, condition=EC.visibility_of_all_elements_located,
               locator=(By.CSS_SELECTOR, '#index .item'))

여기서 우리는 두 가지 방법을 정의합니다.

첫 번째 방법 인 scrape_page는 여전히 일반적인 크롤링 방법이며, 모든 URL 크롤링 및 상태 모니터링 및 예외 처리를 수행 할 수 있으며, 세 개의 매개 변수 url, condition, locator를 수신하며, url 매개 변수는 크롤링 할 페이지의 URL입니다. ; Condition은 페이지 로딩의 판단 조건으로, visible_of_all_elements_located, visibility_of_element_located 등과 같은 expected_conditions의 판단 조건 중 하나 일 수 있습니다. (By.CSS_SELECTOR, '# index .item')과 같은 다중 노드는 목록 페이지의 모든 영화 정보 노드를 가져 오기 위해 CSS 선택기를 통해 #index .item을 검색하는 것을 의미합니다. 또한 크롤링 프로세스에 TimeoutException 감지 기능이 추가되어 해당 노드가 지정된 시간 (여기서는 10 초) 내에로드되지 않으면 TimeoutException 예외가 발생하고 오류 로그가 출력됩니다.

두 번째 방법 인 scrape_index는 목록 페이지를 크롤링하는 방법으로 매개 변수 페이지를 수신하고 scrape_page 메소드를 호출하고 조건 및 로케이터 객체를 전달하여 페이지 크롤링을 완료합니다. 여기에서 조건에 visibility_of_all_elements_located를 사용합니다. 즉, 성공한 것으로 간주되기 전에 모든 노드가로드됩니다.

여기에서 페이지를 크롤링 할 때 결과를 반환 할 필요가 없습니다. scrape_index가 실행 된 후 페이지는 해당 페이지가로드 된 상태에 있기 때문입니다. 브라우저 개체를 사용하여 정보를 추가로 추출 할 수 있습니다.

이제 목록 페이지를로드 할 수 있습니다. 다음 단계는 목록 페이지를 구문 분석하고 세부 정보 페이지의 URL을 추출하는 것입니다. 목록 페이지를 구문 분석하는 방법을 다음과 같이 정의합니다.

from urllib.parse import urljoin
def parse_index():
   elements = browser.find_elements_by_css_selector('#index .item .name')
   for element in elements:
       href = element.get_attribute('href')
       yield urljoin(INDEX_URL, href)

여기서는 find_elements_by_css_selector 메소드를 통해 모든 영화의 이름을 직접 추출한 다음 결과를 탐색하고 get_attribute 메소드를 통해 세부 정보 페이지의 href를 추출하고 urljoin 메소드를 사용하여 완전한 URL로 병합합니다.

마지막으로 주요 방법을 사용하여 위의 방법을 직렬로 연결하여 다음을 달성합니다.

def main():
   try:
       for page in range(1, TOTAL_PAGE + 1):
           scrape_index(page)
           detail_urls = parse_index()
           logging.info('details urls %s', list(detail_urls))
   finally:
       browser.close()

여기에서는 모든 페이지 번호를 탐색하고 각 페이지의 목록 페이지를 차례로 크롤링하고 세부 사항 페이지의 URL을 추출했습니다.

결과는 다음과 같습니다.

2020-03-29 12:03:09,896 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1
2020-03-29 12:03:13,724 - INFO: details urls ['https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx',
...
'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5', 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA==']
2020-03-29 12:03:13,724 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/2
...

출력 내용이 크기 때문에 여기에서 내용의 일부를 생략합니다.

결과를 살펴보면 세부 정보 페이지의 불규칙한 URL이 성공적으로 추출되었음을 알 수 있습니다!

5. 세부 정보 페이지 크롤링

이제 세부 정보 페이지의 URL을 성공적으로 가져올 수 있으므로 세부 정보 페이지의 크롤링을 완료하고 해당 정보를 추출해 보겠습니다.

동일한 로직으로 상세 페이지에 영화 이름이로드되었는지 판단하는 등의 판단 조건을 추가 할 수 있으며, 이는 상세 페이지가 성공적으로로드되었음을 의미합니다. scrape_page 메소드도 호출 할 수 있습니다. 코드 구현은 다음과 같습니다.

def scrape_detail(url):
   scrape_page(url, condition=EC.visibility_of_element_located,
               locator=(By.TAG_NAME, 'h2'))

여기서 우리는 판정 조건으로 visibility_of_element_located를 사용하는데, 이는 단일 요소가 나타나는지 여부를 판단 할 수 있음을 의미합니다. 로케이터의 경우 다음과 같이 영화 이름에 해당하는 노드 인 노드 h2 인 (By.TAG_NAME, 'h2')를 전달합니다. 그림과 같이.
여기에 사진 설명 삽입
scrape_detail 메서드가 실행되고 TimeoutException이 없으면 페이지가 성공적으로로드 된 다음 세부 정보 페이지를 구문 분석하여 원하는 정보를 추출하는 메서드를 정의합니다. 구현은 다음과 같습니다.

def parse_detail():
   url = browser.current_url
   name = browser.find_element_by_tag_name('h2').text
   categories = [element.text for element in browser.find_elements_by_css_selector('.categories button span')]
   cover = browser.find_element_by_css_selector('.cover').get_attribute('src')
   score = browser.find_element_by_class_name('score').text
   drama = browser.find_element_by_css_selector('.drama p').text
   return {
    
    
       'url': url,
       'name': name,
       'categories': categories,
       'cover': cover,
       'score': score,
       'drama': drama
   }

여기에서 URL, 이름, 카테고리, 표지, 점수, 소개 등을 추출하기 위해 parse_detail 메소드를 정의합니다. 추출 방법은 다음과 같습니다.

  • URL : 브라우저 개체의 current_url 속성을 직접 호출하여 현재 페이지의 URL을 가져옵니다.
    이름 : h2 노드 내부의 텍스트를 추출하여 얻을 수 있습니다. 여기에서는 find_element_by_tag_name 메소드를 사용하여 h2를 전달합니다. 이름이있는 노드를 추출한 후 영화 이름 인 text 속성을 호출하여 노드 내부의 텍스트를 추출합니다.
  • 카테고리 : 편의상 CSS 선택기를 통해 카테고리를 추출 할 수 있습니다. 해당 CSS 선택기는 .categories 버튼 스팬입니다. 여러 카테고리 노드를 선택할 수 있습니다. 여기서 find_elements_by_css_selector를 통해 CSS 선택기에 해당하는 여러 카테고리 노드를 추출 할 수 있습니다. , 그런 다음 결과를 차례로 탐색하고 text 속성을 호출하여 노드의 내부 텍스트를 가져옵니다.
  • 표지 : CSS 선택기 .cover를 사용하여 표지에 해당하는 노드를 직접 가져올 수도 있지만 표지의 URL이 src 속성에 해당하므로 여기서는 get_attribute 메서드를 사용하고 src를 전달하여 추출합니다.
  • 점수 : 점수에 해당하는 CSS 선택자는 .score입니다. 위와 같은 방법으로 추출 할 수 있지만 여기서는 클래스 이름을 사용하여 노드를 추출 할 수있는 find_element_by_class_name이라는 메서드를 변경하여 동일한 효과를 얻을 수 있습니다. 그러나 여기에 전달 된 매개 변수는 .score 대신 클래스 점수의 이름입니다. 노드를 추출한 후 text 속성을 호출하여 노드 텍스트를 추출 할 수 있습니다.
  • 소개 : CSS 선택기 .drama p를 사용하여 소개에 해당하는 노드를 직접 가져온 다음 text 속성을 호출하여 텍스트를 추출 할 수도 있습니다.

마지막으로 결과를 사전으로 구성하여 반환 할 수 있습니다.
다음으로이 두 메서드의 호출을 기본 메서드에 추가합니다. 구현은 다음과 같습니다.

def main():
   try:
       for page in range(1, TOTAL_PAGE + 1):
           scrape_index(page)
           detail_urls = parse_index()
           for detail_url in list(detail_urls):
               logging.info('get detail url %s', detail_url)
               scrape_detail(detail_url)
               detail_data = parse_detail()
               logging.info('detail data %s', detail_data)
   finally:
       browser.close()

이런 식으로 목록 페이지를 크롤링 한 후 세부 정보 페이지를 차례로 크롤링하여 각 영화의 특정 정보를 추출 할 수 있습니다.

2020-03-29 12:24:10,723 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1
2020-03-29 12:24:16,997 - INFO: get detail url https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
2020-03-29 12:24:16,997 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
2020-03-29 12:24:19,289 - INFO: detail data {
    
    'url': 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', 'name': '霸王别姬 - Farewell My Concubine', 'categories': ['剧情', '爱情'], 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '影片借一出《霸王别姬》的京戏,牵扯出三个人之间一段随时代风云变幻的爱恨情仇。段小楼(张丰毅 饰)与程蝶衣(张国荣 饰)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐 饰),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。'}
2020-03-29 12:24:19,291 - INFO: get detail url https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy
2020-03-29 12:24:19,291 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy
2020-03-29 12:24:21,524 - INFO: detail data {
    
    'url': 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy', 'name': '这个杀手不太冷 - Léon', 'categories': ['剧情', '动作', '犯罪'], 'cover': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '里昂(让·雷诺 饰)是名孤独的职业杀手,受人雇佣。一天,邻居家小姑娘马蒂尔德(纳塔丽·波特曼 饰)敲开他的房门,要求在他那里暂避杀身之祸。原来邻居家的主人是警方缉毒组的眼线,只因贪污了一小包毒品而遭恶警(加里·奥德曼 饰)杀害全家的惩罚。马蒂尔德 得到里昂的留救,幸免于难,并留在里昂那里。里昂教小女孩使枪,她教里昂法文,两人关系日趋亲密,相处融洽。 女孩想着去报仇,反倒被抓,里昂及时赶到,将女孩救回。混杂着哀怨情仇的正邪之战渐次升级,更大的冲突在所难免……'}
...

이러한 방식으로 세부 정보 페이지 데이터도 추출 할 수 있습니다.

6. 데이터 저장

마지막으로 이전과 같이 데이터 저장 방식을 추가하고 편의상 여기에 JSON 텍스트 파일로 저장합니다. 구현은 다음과 같습니다.

from os import makedirs
from os.path import exists
RESULTS_DIR = 'results'
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
def save_data(data):
   name = data.get('name')
   data_path = f'{RESULTS_DIR}/{name}.json'
   json.dump(data, open(data_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)

여기서의 원리와 구현 방법은 Ajax가 실제 전투 클래스를 크롤링하는 것과 정확히 동일하므로 여기서는 반복하지 않겠습니다.

마지막으로 save_data에 대한 호출을 추가하여 실행 효과를 완전히 확인합니다.

7. 헤드리스

크롤링 프로세스 중에 팝업 브라우저가 방해가된다고 생각되면 Chrome의 헤드리스 모드를 사용하여 크롤링 프로세스 중에 브라우저가 더 이상 팝업되지 않도록 할 수 있으며 크롤링 속도가 더욱 향상됩니다.

다음과 같이 수정하십시오.

options = webdriver.ChromeOptions()
options.add_argument('--headless')
browser = webdriver.Chrome(options=options)

여기서 --headless 매개 변수는 ChromeOptions를 통해 추가 된 다음 ChromeOptions를 사용하여 Chrome을 초기화 할 수 있습니다.

수정 후 코드를 다시 실행하면 Chrome 브라우저가 팝업되지 않고 크롤링 결과가 정확히 동일합니다.

8. 요약

이 수업에서는 케이스를 통해 Selenium의 적용 가능한 시나리오에 대해 배웠고, Selenium의 사용을 더 이해하기 위해 페이지 크롤링을 달성하기 위해 케이스와 함께 Selenium을 사용했습니다.

앞으로 Selenium을 사용할 수있는시기와 Selenium을 사용하여 페이지 크롤링을 완료하는 방법을 알게 될 것입니다.

추천

출처blog.csdn.net/weixin_38819889/article/details/108697172