我正在尝试开发一个简单的网页抓取工具。我想提取没有HTML代码的文本。实际上,我实现了这个目标,但是我发现在某些加载了JavaScript的页面中,我没有获得良好的结果。
例如,如果一些JavaScript代码添加了一些文本,则看不到它,因为当我调用
response = urllib2.urlopen(request)
我得到的原始文本没有添加文本(因为在客户端执行了JavaScript)。
因此,我正在寻找一些解决此问题的想法。
我正在尝试开发一个简单的网页抓取工具。我想提取没有HTML代码的文本。实际上,我实现了这个目标,但是我发现在某些加载了JavaScript的页面中,我没有获得良好的结果。
例如,如果一些JavaScript代码添加了一些文本,则看不到它,因为当我调用
response = urllib2.urlopen(request)
我得到的原始文本没有添加文本(因为在客户端执行了JavaScript)。
因此,我正在寻找一些解决此问题的想法。
Answers:
编辑30 / Dec / 2017:这个答案出现在Google搜索的顶部结果中,所以我决定更新它。旧的答案仍然是最后。
dryscape不再维护,并且dryscape开发人员推荐的库仅是Python 2。我发现使用Selenium的python库和Phantom JS作为Web驱动程序足够快且容易地完成工作。
一旦安装了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>
如果没有javascript,它会说:No javascript support
和javascript:Yay! Supports javascript
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>
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驱动的网站。
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>
@Expenzor
我正在窗户上工作。PhantomJS正常工作。
我们无法获得正确的结果,因为任何JavaScript生成的内容都需要在DOM上呈现。当我们获取HTML页面时,我们将获取未经javascript修改的初始DOM。
因此,我们需要在爬网页面之前呈现javascript内容。
由于在该线程中已经多次提到硒(有时还提到硒的缓慢程度),因此我将列出另外两种可能的解决方案。
解决方案1:这是一个非常不错的教程,说明如何使用Scrapy来爬网javascript生成的内容,我们将遵循这一点。
我们将需要:
在我们的机器上安装了Docker。到目前为止,这是对其他解决方案的加分,因为它利用了与操作系统无关的平台。
按照针对我们相应操作系统列出的说明安装Splash。
从初始文档中引用:
Splash是一种javascript渲染服务。这是一个带有HTTP API的轻量级Web浏览器,使用Twisted和QT5在Python 3中实现。
本质上,我们将使用Splash渲染Javascript生成的内容。
运行启动服务器:sudo docker run -p 8050:8050 scrapinghub/splash
。
安装scrapy-splash插件:pip install scrapy-splash
假设我们已经创建了一个Scrapy项目(如果没有创建,请创建一个),我们将按照指南进行操作并更新settings.py
:
然后转到您的草书项目
settings.py
并设置以下中间件:DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, }
Splash服务器的URL(如果您使用的是Win或OSX,则应为Docker计算机的URL:如何从主机获取Docker容器的IP地址?):
SPLASH_URL = 'http://localhost:8050'
最后,您还需要设置以下值:
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
最后,我们可以使用SplashRequest
:
在普通蜘蛛中,您可以使用Request对象来打开URL。如果要打开的页面包含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(例如,抓取Web)尽可能简单直观。
安装requests-html: pipenv install requests-html
向页面的网址发出请求:
from requests_html import HTMLSession
session = HTMLSession()
r = session.get(a_page_url)
渲染响应以获取Javascript生成的位:
r.html.render()
最后,该模块似乎提供了抓取功能。
另外,我们可以尝试使用r.html
我们已渲染的对象使用BeautifulSoup的有据可查的方法。
r.html.html
对象中的JavaScript注入到页面中的所有iframe 。
也许硒能做到这一点。
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
如果您曾经使用过该Requests
模块用于python,我最近发现开发人员创建了一个名为的新模块,该模块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视频,该视频演示了该模块的工作原理。
摘自一篇出色的博客文章,这似乎也是一个不错的解决方案
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
听起来您真正想要的数据可以通过主页上某些javascript调用的辅助URL进行访问。
尽管您可以尝试在服务器上运行javascript来解决此问题,但一种更简单的方法可能是使用Firefox加载页面并使用Charles或Firebug之类的工具来确切地确定该辅助URL是什么。然后,您可以直接在该URL中查询您感兴趣的数据。
硒最适合抓取JS和Ajax内容。
查看本文以使用Python从网络中提取数据
$ 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)
容易吧?
我个人更喜欢在单独的容器中使用scrapy和硒,并进行docker化。通过这种方式,您可以轻松地安装和抓取几乎所有都以一种形式或另一种形式包含javascript的现代网站。这是一个例子:
使用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文件中的以下行来启用此中间件软件:
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
还可以检查硒容器的名称是否与我们传递给我们的scraper容器的环境变量(此处为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页面上,您可以从这里获取
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")
PS您可以在这里找到更多等待条件
您需要在脚本的页面不同部分(仅举几例)中使用urllib,requests,BeautifulSoup和Selenium Web驱动程序。
有时,仅使用这些模块之一就可以满足您的需求。
有时您需要两个,三个或所有这些模块。
有时您需要关闭浏览器上的js。
有时,您的脚本中需要标题信息。
通常,几个月后,无需修改爬网程序,就无法永久删除同一网站,也无法永久永久删除同一网站。但是它们都可以被刮掉!有意志的地方肯定有办法。
如果您需要在未来持续不断地抓取数据,则只需抓取您需要的所有内容并将其存储在pickle中的.dat文件中即可。
只需继续搜索如何尝试使用这些模块,然后将错误复制并粘贴到Google中即可。
使用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)