我试图开发一个简单的网页刮板。我想提取没有HTML代码的文本。它适用于普通HTML,但不适用于JavaScript代码添加文本的某些页面。

例如,如果一些JavaScript代码添加了一些文本,我不能看到它,因为当我调用:

response = urllib2.urlopen(request)

我得到了原始文本而没有添加的文本(因为JavaScript是在客户端执行的)。

所以,我正在寻找一些解决这个问题的想法。


当前回答

也许硒可以做到。

from selenium import webdriver
import time

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

其他回答

如果你以前曾经使用过python的Requests模块,我最近发现开发人员创建了一个名为Requests- html的新模块,现在它也有呈现JavaScript的能力。

你也可以访问https://html.python-requests.org/来了解更多关于这个模块的信息,或者如果你只对呈现JavaScript感兴趣,那么你可以访问https://html.python-requests.org/?#javascript-support来直接学习如何使用该模块使用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上演示该模块如何工作的视频。

Selenium是抓取JS和Ajax内容的最佳工具。

查看这篇文章,了解如何使用Python从web中提取数据

$ pip install selenium

然后下载Chrome webdriver。

from selenium import webdriver

browser = webdriver.Chrome()

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

nav = browser.find_element_by_id("mainnav")

print(nav.text)

容易,对吧?

尝试直接访问API

在抓取中常见的场景是网页从API端点异步请求数据。一个最小的例子是以下网站:

身体< > < >脚本 fetch(“https://jsonplaceholder.typicode.com/posts/1”) .then(res => { if (!res.ok)抛出错误(res.status); 返回res.json (); }) .then(data => { //页面加载后通过JS动态注入数据 document.body.innerText = data.title; }) .catch(err => console.error(err)) ; > < /脚本 身体< / >

在许多情况下,API将受到CORS或访问令牌的保护,或速率限制过高,但在其他情况下,它是公开可访问的,您可以完全绕过网站。对于CORS问题,你可以在任何地方尝试CORS。

一般的过程是使用浏览器的开发人员工具的网络选项卡来搜索页面发出的请求,以获得您想要抓取的数据的关键字/子字符串。通常,您会看到一个不受保护的API请求端点,该端点带有一个JSON有效负载,您可以直接使用urllib或请求模块访问该有效负载。上面的可运行代码片段就是这种情况,你可以用它来练习。点击“运行片段”后,下面是我如何在我的网络选项卡中找到端点:

这个例子是虚构的;从静态标记来看,端点URL可能不明显,因为它可以被动态组装、缩小并隐藏在数十个其他请求和端点之下。网络请求还将显示任何相关的请求有效负载细节,例如您可能需要的访问令牌。

在获取端点URL和相关细节后,使用标准HTTP库在Python中构建一个请求并请求数据:

>>> import requests
>>> res = requests.get("https://jsonplaceholder.typicode.com/posts/1")
>>> data = res.json()
>>> data["title"]
'sunt aut facere repellat provident occaecati excepturi optio reprehenderit'

当你可以摆脱它时,这往往比使用Selenium、Pyppeteer、Scrapy或其他流行的抓取库更容易、更快、更可靠。

如果您很不幸,数据没有通过API请求以良好的格式返回数据,那么它可能是原始浏览器负载的一部分,在<script>标记中,作为JSON字符串或(更可能的是)JS对象。例如:

<body> <script> var someHardcodedData = { 用户 ID: 1, 编号: 1, 题目: “Sunt aut facere repellat provident occaecati excepturi optio reprehenderit”, Body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto' }; document.body.textContent = someHardcodedData.title; </script> </body>

没有一种万能的方法来获取这些数据。基本技术是使用BeautifulSoup访问<script>标记文本,然后应用正则表达式或解析来提取对象结构、JSON字符串或数据可能采用的任何格式。下面是上面所示的示例结构的概念证明:

import json
import re
from bs4 import BeautifulSoup

# pretend we've already used requests to retrieve the data, 
# so we hardcode it for the purposes of this example
text = """
<body>
<script>
  var someHardcodedData = {
    userId: 1,
    id: 1,
    title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 
    body: 'quia et suscipit\nsuscipit recusandae con sequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'
  };
  document.body.textContent = someHardcodedData.title;
</script>
</body>
"""
soup = BeautifulSoup(text, "lxml")
script_text = str(soup.select_one("script"))
pattern = r"title: '(.*?)'"
print(re.search(pattern, script_text, re.S).group(1))

看看下面这些解析JS对象的资源,它们不是很有效的JSON:

如何将原始javascript对象转换为python字典? 如何修复JSON键值没有双引号?

以下是一些使用API绕过抓取的额外案例研究/概念证明:

如何使用Python beautifulsoup将yelp评论和星级评分刮到CSV Beautiful Soup对现有元素返回None 从BeautifulSoup Python中提取数据 通过POST收集Bandcamp粉丝(使用一种混合方法,即向网站发出初始请求,从使用BeautifulSoup的标记中提取一个令牌,然后在对JSON端点的第二个请求中使用)

如果所有这些都失败了,请尝试本线程中列出的许多动态抓取库中的一个。

也许硒可以做到。

from selenium import webdriver
import time

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

我个人更喜欢在单独的容器中使用scrapy和selenium和dockerizing。通过这种方式,你既可以轻松安装,也可以抓取几乎所有包含某种形式javascript的现代网站。这里有一个例子:

使用scrapy startproject创建你的scraper并编写你的蜘蛛,骨架可以像这样简单:

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)

真正的魔力发生在middleware .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文件中取消下一行的注释来启用这个中间件:

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

接下来是dockerization。从一个轻量级映像创建Dockerfile(我在这里使用python Alpine),复制你的项目目录到它,安装要求:

# 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容器的名称是否与传递给scraper容器的环境变量的名称相匹配(在这里,它是SELENIUM_LOCATION=samplecrawler_selenium_1)。

使用docker exec -ti YOUR_CONTAINER_NAME sh进入你的刮板容器,我的命令是docker exec -ti samplecrawler_my_scraper_1 sh, cd到正确的目录下,并用scrapy爬行my_spider运行你的刮板。

所有内容都在我的github页面上,你可以从这里获取