Programing

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

lottogame 2020. 6. 10. 23:20
반응형

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


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

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

response = urllib2.urlopen(request)

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

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


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>

자바 스크립트로 생성 된 콘텐츠를 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.


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

from selenium import webdriver
import time

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

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 동영상을 시청합니다.


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

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

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

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


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)

쉬워요?


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

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')

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

를 사용 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,}

Next for dockerization. Create your Dockerfile from a lightweight image (I'm using python Alpine here), copy your project directory to it, install requirements:

# 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

And finally bring it all together in 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

Run docker-compose up -d. If you're doing this the first time it will take a while for it to fetch the latest selenium/standalone-chrome and the build your scraper image as well.

Once it's done, you can check that your containers are running with docker ps and also check that the name of the selenium container matches that of the environment variable that we passed to our scraper container (here, it was SELENIUM_LOCATION=samplecrawler_selenium_1).

Enter your scraper container with docker exec -ti YOUR_CONTAINER_NAME sh , the command for me was docker exec -ti samplecrawler_my_scraper_1 sh, cd into the right directory and run your scraper with scrapy crawl my_spider.

The entire thing is on my github page and you can get it from here


You'll want to use urllib, requests, beautifulSoup and selenium web driver in your script for different parts of the page, (to name a few).
Sometimes you'll get what you need with just one of these modules.
Sometimes you'll need two, three, or all of these modules.
Sometimes you'll need to switch off the js on your browser.
Sometimes you'll need header info in your script.
No websites can be scraped the same way and no website can be scraped in the same way forever without having to modify your crawler, usually after a few months. But they can all be scraped! Where there's a will there's a way for sure.
If you need scraped data continuously into the future just scrape everything you need and store it in .dat files with pickle.
Just keep searching how to try what with these modules and copying and pasting your errors into the Google.


A mix of BeautifulSoup and Selenium works very well for me.

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")

P.S. You can find more wait conditions here


Using 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)

참고URL : https://stackoverflow.com/questions/8049520/web-scraping-javascript-page-with-python

반응형