Python을 사용한 웹 스크랩 핑 JavaScript 페이지


178

간단한 웹 스크레이퍼를 개발하려고합니다. HTML 코드없이 텍스트를 추출하고 싶습니다. 실제로이 목표를 달성했지만 JavaScript 가로 드 된 일부 페이지에서는 좋은 결과를 얻지 못했습니다.

예를 들어, 일부 JavaScript 코드가 텍스트를 추가하면 텍스트를 볼 수 없습니다.

response = urllib2.urlopen(request)

JavaScript가 클라이언트에서 실행되기 때문에 추가 된 텍스트없이 원본 텍스트를 얻습니다.

그래서이 문제를 해결할 아이디어를 찾고 있습니다.


2
더 무거운 것이 필요할 수 있습니다 .Selenium 또는 Watir를 사용해보십시오.
wim

2
Java 에서이 작업을 성공적으로 수행했습니다 (Cobra 툴킷 lobobrowser.org/cobra.jsp 사용 ) 파이썬에서 해킹하고 싶기 때문에 (항상 좋은 선택) 다음 두 가지 옵션을 권장합니다.- packtpub.com / article/ 웹 스크래핑 -와 - 파이썬 - 파트 2 - blog.databigbang.com/web-scraping-ajax-and-javascript-sites
bpgergo

답변:


203

2017 년 12 월 30 일 수정 :이 답변은 Google 검색의 최상위 결과에 표시되므로 업데이트하기로 결정했습니다. 오래된 대답은 여전히 ​​끝났습니다.

dryscape는 더 이상 유지되지 않으며 라이브러리 dryscape 개발자는 Python 2 만 권장합니다. 나는 Phantom JS와 함께 Selenium의 python 라이브러리를 웹 드라이버로 사용하여 빠르고 쉽게 작업을 수행하는 것을 발견했습니다.

Phantom JS를 설치했으면 phantomjs바이너리가 현재 경로에서 사용 가능한지 확인하십시오 .

phantomjs --version
# result:
2.1.1

예를 들어, 다음 HTML 코드로 샘플 페이지를 작성했습니다. ( 링크 ) :

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Javascript scraping test</title>
</head>
<body>
  <p id='intro-text'>No javascript support</p>
  <script>
     document.getElementById('intro-text').innerHTML = 'Yay! Supports javascript';
  </script> 
</body>
</html>

자바 스크립트가 없으면 다음 No javascript support과 같이 나타납니다.Yay! Supports javascript

JS 지원이없는 스크래핑 :

import requests
from bs4 import BeautifulSoup
response = requests.get(my_url)
soup = BeautifulSoup(response.text)
soup.find(id="intro-text")
# Result:
<p id="intro-text">No javascript support</p>

JS 지원으로 스크래핑 :

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(my_url)
p_element = driver.find_element_by_id(id_='intro-text')
print(p_element.text)
# result:
'Yay! Supports javascript'

Python 라이브러리 dryscrape 를 사용 하여 Javascript 기반 웹 사이트를 긁을 수도 있습니다 .

JS 지원으로 스크래핑 :

import dryscrape
from bs4 import BeautifulSoup
session = dryscrape.Session()
session.visit(my_url)
response = session.body()
soup = BeautifulSoup(response)
soup.find(id="intro-text")
# Result:
<p id="intro-text">Yay! Supports javascript</p>

16
안타깝게도 Windows는 지원되지 않습니다.
Expenzor

1
Windows에서 프로그래밍하는 다른 대안이 있습니까?
Hoshiko86

2
@Expenzor나는 창문에서 일하고 있습니다. PhantomJS는 잘 작동합니다.
Aakash Choubey

17
PhantomJS는 단종되었으며 현재는 헤드리스를 지원하는 Chrome에 비추어 더 이상 적극적으로 개발되지 않고 있습니다. 헤드리스 크롬 / 파이어 폭스 사용을 권장합니다.
sytech

3
셀레늄 지원과 PhantomJS 자체입니다. github.com/ariya/phantomjs/issues/15344
sytech

73

자바 스크립트로 생성 된 콘텐츠를 DOM에서 렌더링해야하므로 올바른 결과를 얻지 못했습니다. HTML 페이지를 가져올 때 자바 스크립트 DOM에 의해 수정되지 않은 초기를 가져옵니다.

따라서 페이지를 크롤링하기 전에 자바 스크립트 컨텐츠를 렌더링해야합니다.

이 스레드에서 셀레늄이 이미 여러 번 언급되었으므로 (그리고 때때로 느리게 진행되는 경우도 언급되었으므로) 가능한 두 가지 다른 솔루션을 나열합니다.


해결 방법 1 : 이것은 Scrapy를 사용하여 자바 스크립트로 생성 된 콘텐츠를 크롤링하는 방법대한 매우 유용한 자습서 입니다.

우리가 필요한 것 :

  1. 기계에 Docker가 설치되었습니다. 이것은 OS 독립적 인 플랫폼을 사용하므로이 시점까지 다른 솔루션에 비해 장점입니다.

  2. 해당 OS에 대해 나열된 지침에 따라 Splash를 설치 하십시오.
    스플래시 문서에서 인용 :

    Splash는 자바 스크립트 렌더링 서비스입니다. Twisted 및 QT5를 사용하여 Python 3에서 구현 된 HTTP API를 갖춘 경량 웹 브라우저입니다.

    기본적으로 Splash를 사용하여 Javascript로 생성 된 컨텐츠를 렌더링합니다.

  3. 스플래시 서버를 실행하십시오 sudo docker run -p 8050:8050 scrapinghub/splash..

  4. scrapy-splash 플러그인을 설치하십시오 :pip install scrapy-splash

  5. 우리는 이미 (, 아니라면 만든 Scrapy 프로젝트가 있다고 가정 의 메이크업 하나하자 , 우리는 가이드를 따라 업데이트됩니다) settings.py:

    그런 다음 귀찮은 프로젝트로 이동하여 settings.py다음 미들웨어를 설정하십시오.

    DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
    }
    

    Splash 서버의 URL (Win 또는 OSX를 사용하는 경우 도커 시스템의 URL이어야합니다 . 호스트에서 Docker 컨테이너의 IP 주소를 얻는 방법은 무엇입니까? ) :

    SPLASH_URL = 'http://localhost:8050'

    마지막으로 다음 값도 설정해야합니다.

    DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
    HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
    
  6. 마지막으로 다음을 사용할 수 있습니다 SplashRequest.

    일반 스파이더에는 URL을 여는 데 사용할 수있는 Request 객체가 있습니다. 열려는 페이지에 JS 생성 데이터가 포함되어 있으면 SplashRequest (또는 SplashFormRequest)를 사용하여 페이지를 렌더링해야합니다. 다음은 간단한 예입니다.

    class MySpider(scrapy.Spider):
        name = "jsscraper"
        start_urls = ["http://quotes.toscrape.com/js/"]
    
        def start_requests(self):
            for url in self.start_urls:
            yield SplashRequest(
                url=url, callback=self.parse, endpoint='render.html'
            )
    
        def parse(self, response):
            for q in response.css("div.quote"):
            quote = QuoteItem()
            quote["author"] = q.css(".author::text").extract_first()
            quote["quote"] = q.css(".text::text").extract_first()
            yield quote
    

    SplashRequest는 URL을 html로 렌더링하고 callback (parse) 메서드에서 사용할 수있는 응답을 반환합니다.


해결 방법 2 : 이 실험을 지금 바로 호출 해 봅시다 (2018 년 5 월) ...
이 솔루션은 현재 Python 버전 3.6 에만 해당됩니다.

요청 모듈 을 알고 있습니까?
이제 웹 크롤링 작은 형제가 있습니다 : requests-HTML :

이 라이브러리는 HTML 구문 분석 (예 : 웹 스크랩)을 가능한 한 간단하고 직관적으로 만듭니다.

  1. 설치 요청 -html : pipenv install requests-html

  2. 페이지 URL을 요청하십시오.

    from requests_html import HTMLSession
    
    session = HTMLSession()
    r = session.get(a_page_url)
    
  3. Javascript 생성 비트를 얻기 위해 응답을 렌더링하십시오.

    r.html.render()

마지막 으로이 모듈은 스크래핑 기능 을 제공하는 것으로 보입니다 .
또는 방금 렌더링 한 객체 와 함께 BeautifulSoup 을 잘 문서화 한 방법으로 시도 할 수 있습니다 r.html.


.render ()를 호출 한 후 JS 비트가로드 된 전체 HTML 내용을 얻는 방법을 확장 할 수 있습니까? 그 시점 이후에 갇혀있다. 일반적으로 r.html.html객체의 JavaScript에서 페이지에 주입 된 모든 iframe이 표시되지 않습니다 .
anon58192932

@ anon58192932 현재 이것은 실험적인 솔루션이므로 결과로 정확히 무엇을 달성하려고하는지 알 수 없으므로 실제로 아무 것도 제안 할 수 없습니다 ... 여기에 새로운 질문을 만들 수 있습니다. 아직 해결책을 찾지 못했습니다
John Moutafis

2
이 오류가 발생했습니다 : RuntimeError : 기존 이벤트 루프 내에서 HTMLSession을 사용할 수 없습니다. 대신 AsyncHTMLSession을 사용하십시오.
HuckIt

1
@HuckIt 이것은 알려진 문제인 것 같습니다 : github.com/psf/requests-html/issues/140
John Moutafis

47

아마도 셀레늄 이 그것을 할 수 있습니다.

from selenium import webdriver
import time

driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source

3
셀레늄은 이런 종류의 것들에 대해 실제로 무겁습니다. PhantomJS를 사용하지 않으면 불필요하게 느리고 브라우저 헤드가 필요하지만 작동합니다.
Joshua Hedges

@JoshuaHedges 다른 표준 브라우저를 헤드리스 모드로 실행할 수 있습니다.
reynoldsnlp

22

Requests이전에 파이썬 에서 모듈을 사용한 적이 있다면 최근에 개발자 Requests-HTML가 JavaScript를 렌더링 할 수 있는 새로운 모듈을 만들었습니다 .

https://html.python-requests.org/ 를 방문 하여이 모듈에 대해 자세히 알아 보거나 JavaScript 렌더링에 관심이있는 경우 https://html.python-requests.org/?#javascript 를 방문 하십시오. -지원 모듈을 사용하여 Python을 사용하여 JavaScript를 렌더링하는 방법을 직접 학습합니다.

기본적으로 Requests-HTML모듈 을 올바르게 설치하면 위의 링크에 표시된 다음 예제는 이 모듈을 사용하여 웹 사이트를 긁어 내고 웹 사이트에 포함 된 JavaScript를 렌더링하는 방법을 보여줍니다.

from requests_html import HTMLSession
session = HTMLSession()

r = session.get('http://python-requests.org/')

r.html.render()

r.html.search('Python 2 will retire in only {months} months!')['months']

'<time>25</time>' #This is the result.

최근 YouTube 동영상에서 이에 대해 배웠습니다. 여기를 클릭하십시오! 모듈 작동 방식을 보여주는 YouTube 동영상을 시청합니다.


3
이 모듈은 Python 3.6 만 지원합니다.
nat5142

1
이 오류가 발생했습니다. SSLError : HTTPSConnectionPool (host = 'docs.python-requests.org', port = 443) : 최대 재 시도가 url을 초과했습니다 : / (SSLError (SSLError (1, '[SSL : TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert 내부 오류 (_ssl.c : 1045) ')))
HuckIt

@HuckIt appologies 나는 그 오류에 익숙하지 않지만, 오류는 당신이 접근하려고하는 웹 사이트에 SSL 인증서 관련 문제가 있었을 것 같습니다. 죄송하지만 이것은 해결책이 아니지만 스택 오버플로 (아직 요청되지 않은 경우)에서 새로운 질문을하고 사용중인 웹 사이트 URL 및 코드와 같은 자세한 내용을 알려주는 것이 좋습니다.
SShah

후드 아래에 크롬을 사용하는 것 같습니다. 그래도 잘 작동합니다
Sid

14

이것은 훌륭한 블로그 게시물 에서 가져온 좋은 솔루션 인 것 같습니다.

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *  
from lxml import html 

#Take this class for granted.Just use result of rendering.
class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)  
    self.loadFinished.connect(self._loadFinished)  
    self.mainFrame().load(QUrl(url))  
    self.app.exec_()  

  def _loadFinished(self, result):  
    self.frame = self.mainFrame()  
    self.app.quit()  

url = 'http://pycoders.com/archive/'  
r = Render(url)  
result = r.frame.toHtml()
# This step is important.Converting QString to Ascii for lxml to process

# The following returns an lxml element tree
archive_links = html.fromstring(str(result.toAscii()))
print archive_links

# The following returns an array containing the URLs
raw_links = archive_links.xpath('//div[@class="campaign"]/a/@href')
print raw_links

12

실제로 찾고있는 데이터는 기본 페이지의 일부 자바 스크립트에서 호출 한 보조 URL을 통해 액세스 할 수있는 것처럼 들립니다.

서버에서 자바 스크립트를 실행하여이 문제를 처리 할 수는 있지만 Firefox를 사용하여 페이지를로드하고 Charles 또는 Firebug 와 같은 도구를 사용하여 해당 보조 URL이 무엇인지 정확히 식별 하는 간단한 방법이 있습니다 . 그런 다음 관심있는 데이터에 대해 해당 URL을 직접 쿼리하면됩니다.


@Kris 누군가가 이것에 걸려 넘어져서 셀레늄과 같은 무거운 것 대신에 그것을 시도하고 싶을 경우를 대비하여 짧은 예가 있습니다. 이것은 맥 매스터 - 카 웹 사이트에 육각 너트의 제품 상세 페이지가 열립니다. 웹 사이트 콘텐츠는 대부분 자바 스크립트를 사용하여 가져 오며 기본 페이지 정보는 거의 없습니다. 브라우저 개발자 도구를 열고 네트워크 탭으로 이동하여 페이지를 새로 고치면 페이지의 모든 요청을보고 관련 데이터 (이 경우 부품 세부 사항 html)를 찾을 수 있습니다.
SweepingsDemon

이는 Firefox devtool 네트워크 탭에있는 다른 URL로, 대부분의 부품 정보에 대한 html을 포함하고 쉽게 긁기 위해 다른 부품 정보로 쉽게 이동하는 데 필요한 일부 매개 변수를 표시합니다. 이 특정 예제는 다른 Javascript 함수에 의해 가격이 생성되므로 특별히 유용하지는 않지만 Stephen의 조언을 따르고 자하는 사람에게 소개하는 데 충분합니다.
SweepingsDemon

12

Selenium은 JS 및 Ajax 컨텐츠를 스크랩하는 데 가장 적합합니다.

Python을 사용하여 웹에서 데이터추출하려면 이 기사를 확인하십시오.

$ pip install selenium

그런 다음 Chrome 웹 드라이버를 다운로드하십시오.

from selenium import webdriver

browser = webdriver.Chrome()

browser.get("https://www.python.org/")

nav = browser.find_element_by_id("mainnav")

print(nav.text)

쉬운가요?


8

웹 드라이버를 사용하여 자바 스크립트를 실행할 수도 있습니다.

from selenium import webdriver

driver = webdriver.Firefox()
driver.get(url)
driver.execute_script('document.title')

또는 변수에 값을 저장

result = driver.execute_script('var text = document.title ; return var')

또는 당신은 driver.title속성을 사용할 수 있습니다
Corey Goldberg

8

나는 개인적으로 scrapy와 selenium을 사용하고 별도의 컨테이너에 도커를 사용하는 것을 선호합니다. 이렇게하면 최소한의 번거 로움과 크롤링이 가능한 최신 웹 사이트를 모두 설치할 수 있습니다. 예를 들면 다음과 같습니다.

를 사용 scrapy startproject하여 스크레이퍼를 만들고 스파이더를 작성하십시오. 골격은 다음과 같이 간단 할 수 있습니다.

import scrapy


class MySpider(scrapy.Spider):
    name = 'my_spider'
    start_urls = ['https://somewhere.com']

    def start_requests(self):
        yield scrapy.Request(url=self.start_urls[0])


    def parse(self, response):

        # do stuff with results, scrape items etc.
        # now were just checking everything worked

        print(response.body)

실제 마법은 middlewares.py에서 발생합니다. 다운로더 미들웨어의 두 가지 방법을 덮어 쓰기, __init__그리고 process_request다음과 같은 방법으로 :

# import some additional modules that we need
import os
from copy import deepcopy
from time import sleep

from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver

class SampleProjectDownloaderMiddleware(object):

def __init__(self):
    SELENIUM_LOCATION = os.environ.get('SELENIUM_LOCATION', 'NOT_HERE')
    SELENIUM_URL = f'http://{SELENIUM_LOCATION}:4444/wd/hub'
    chrome_options = webdriver.ChromeOptions()

    # chrome_options.add_experimental_option("mobileEmulation", mobile_emulation)
    self.driver = webdriver.Remote(command_executor=SELENIUM_URL,
                                   desired_capabilities=chrome_options.to_capabilities())


def process_request(self, request, spider):

    self.driver.get(request.url)

    # sleep a bit so the page has time to load
    # or monitor items on page to continue as soon as page ready
    sleep(4)

    # if you need to manipulate the page content like clicking and scrolling, you do it here
    # self.driver.find_element_by_css_selector('.my-class').click()

    # you only need the now properly and completely rendered html from your page to get results
    body = deepcopy(self.driver.page_source)

    # copy the current url in case of redirects
    url = deepcopy(self.driver.current_url)

    return HtmlResponse(url, body=body, encoding='utf-8', request=request)

settings.py 파일에서 다음 줄의 주석 처리를 제거하여이 middlware를 활성화하는 것을 잊지 마십시오.

DOWNLOADER_MIDDLEWARES = {
'sample_project.middlewares.SampleProjectDownloaderMiddleware': 543,}

도 커화를위한 다음. 당신의 작성 Dockerfile요구를 설치, 경량의 이미지에서 (내가 사용 파이썬 여기 알파인)를 여기에 프로젝트 디렉토리를 복사 :

# Use an official Python runtime as a parent image
FROM python:3.6-alpine

# install some packages necessary to scrapy and then curl because it's  handy for debugging
RUN apk --update add linux-headers libffi-dev openssl-dev build-base libxslt-dev libxml2-dev curl python-dev

WORKDIR /my_scraper

ADD requirements.txt /my_scraper/

RUN pip install -r requirements.txt

ADD . /scrapers

그리고 마지막으로 모든 것을 하나로 모으십시오 docker-compose.yaml.

version: '2'
services:
  selenium:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    shm_size: 1G

  my_scraper:
    build: .
    depends_on:
      - "selenium"
    environment:
      - SELENIUM_LOCATION=samplecrawler_selenium_1
    volumes:
      - .:/my_scraper
    # use this command to keep the container running
    command: tail -f /dev/null

를 실행하십시오 docker-compose up -d. 이 작업을 처음 수행하는 경우 최신 셀레늄 / 독립 실행 형 크롬을 가져오고 스크레이퍼 이미지도 빌드하는 데 시간이 걸립니다.

완료되면 컨테이너가 실행 중인지 docker ps확인하고 셀레늄 컨테이너의 이름이 스크레이퍼 컨테이너에 전달한 환경 변수의 이름과 일치하는지 확인할 수 있습니다 (여기서는 SELENIUM_LOCATION=samplecrawler_selenium_1).

docker exec -ti YOUR_CONTAINER_NAME sh, 와 함께 스크레이퍼 컨테이너를 입력 docker exec -ti samplecrawler_my_scraper_1 sh하고 올바른 디렉토리에 cd을 사용하여 스크레이퍼를 실행하십시오 scrapy crawl my_spider.

모든 것은 내 github 페이지에 있으며 여기 에서 얻을 수 있습니다


5

BeautifulSoup과 Selenium의 혼합은 저에게 아주 잘 작동합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup as bs

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
    try:
        element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))) #waits 10 seconds until element is located. Can have other wait conditions  such as visibility_of_element_located or text_to_be_present_in_element

        html = driver.page_source
        soup = bs(html, "lxml")
        dynamic_text = soup.find_all("p", {"class":"class_name"}) #or other attributes, optional
    else:
        print("Couldnt locate element")

추신 : 여기에서 더 많은 대기 조건을 찾을 수 있습니다


4

스크립트에서 페이지의 다른 부분에 대해 urllib, requests, beautifulSoup 및 selenium 웹 드라이버를 사용하고 싶을 것입니다.
때로는 이러한 모듈 중 하나만으로 필요한 것을 얻을 수 있습니다.
때로는 이러한 모듈이 2 개, 3 개 또는 모두 필요합니다.
때로는 브라우저에서 js를 꺼야 할 수도 있습니다.
때로는 스크립트에 헤더 정보가 필요할 수 있습니다.
일반적으로 몇 달 후에는 크롤러를 수정하지 않고도 동일한 방식으로 웹 사이트를 스크랩 할 수 없으며 동일한 방식으로 웹 사이트를 스크랩 할 수 없습니다. 그러나 그들은 모두 긁을 수 있습니다! 의지가있는 곳에 확실한 방법이 있습니다.
미래에 지속적으로 스크랩 된 데이터가 필요한 경우 필요한 모든 것을 긁어 내고 피클과 함께 .dat 파일에 저장하십시오.
이 모듈로 무엇을 시도하고 오류를 Google에 복사하여 붙여 넣는 방법을 계속 검색하십시오.


3

PyQt5 사용

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage
import sys
import bs4 as bs
import urllib.request


class Client(QWebEnginePage):
    def __init__(self,url):
        global app
        self.app = QApplication(sys.argv)
        QWebEnginePage.__init__(self)
        self.html = ""
        self.loadFinished.connect(self.on_load_finished)
        self.load(QUrl(url))
        self.app.exec_()

    def on_load_finished(self):
        self.html = self.toHtml(self.Callable)
        print("Load Finished")

    def Callable(self,data):
        self.html = data
        self.app.quit()

# url = ""
# client_response = Client(url)
# print(client_response.html)

1

이틀 동안이 질문에 대한 답을 찾으려고 노력했습니다. 많은 답변이 다른 문제로 연결됩니다. 그러나 위의 뱀의 대답은 실제로 요점입니다. 가장 짧고 간단한 솔루션입니다. 마지막 단어 "var"변수 이름을 나타내 므로 다음과 같이 사용해야합니다.

 result = driver.execute_script('var text = document.title ; return text')

이것은 별도의 답변이 아니라 뱀의 답변에 대한 의견이어야합니다.
Yserbius

1
명백하다. 그러나 나는 다른 사람의 대답에 대해 아직 50 명의 담당자가 없습니다.
Abd_bgc

0

내 웹 스크래핑 프로젝트 에서이 같은 문제를 해결해야했습니다. 내가 처리 한 방법은 python requests 라이브러리를 사용하여 JS를로드하지 않고 API에 직접 http 요청을하는 것입니다.

파이썬 요청 라이브러리는이를 위해 잘 작동하며 inspect 요소를 사용하고 네트워크 탭으로 이동하여 http 요청을 볼 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.