用 Python 执行网页的脚本

Posted

技术标签:

【中文标题】用 Python 执行网页的脚本【英文标题】:executing the script of a webpage with Python 【发布时间】:2014-09-16 03:56:29 【问题描述】:

我正在尝试抓取一个充满 javascript 的页面。网址是:

http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840

我使用以下代码来获取数据。显然这段代码应该处理 javascript 并返回一个完整的 html 文件,但它没有。可能存在时间问题,如果是这样,我不太清楚你在哪里延迟 proram 以允许完整的 html。

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

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

def getHtml(str_url):
    r_html = Render(str_url)
    html = r_html.frame.toHtml()
    return html

str_url = 'http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840'
str_html = getHtml(str_url)
print(str_html)

如果您从 Web 浏览器请求页面源,这给了我您会得到的 html。当然,页面上还有更多内容,因为所有表格都充满了 javascript 函数。使用 Firebug,我要查找的表的 id 是“sharesInIndexTable。我真正想抓取的项目是每个公司名称下的链接 - 但是可以访问整个表以使用 beautifulsoup 进行解析会更好。从这张表中,应该能够找到“嘉士伯”一词(作为查看 AJAX 是否已完全加载的潜在测试)。然后我试图找出解析 DOM 的方法并尝试了这个:

import sys
from PyQt4 import QtGui, QtCore, QtWebKit

class Sp():
    def printit(self):        
        data = self.webView.page().mainFrame().findFirstElement('id="sharesInIndexTable"')
    print(data)       

def main(self):
    self.webView = QtWebKit.QWebView()
    self.webView.load(QtCore.QUrl("http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840"))
    QtCore.QObject.connect(self.webView,QtCore.SIGNAL("loadFinished(bool)"),self.printit)

    app = QtGui.QApplication(sys.argv)
    s = Sp()
    s.main()
    sys.exit(app.exec_())

我从中得到的只是位于 0x03294830 的 PyQt4.QtWebkit.QWebElement 对象(您的结果可能会有所不同)。无论我试图将此地址转换为可读格式,都失败了。这段代码似乎也运行了两次。 然后我尝试了这个(有点适合我的需要):

#!/usr/bin/python

# These lines will get us the modules we need.
from PyQt4.QtCore import QUrl, SIGNAL
from PyQt4.QtGui import QApplication
from PyQt4.QtWebKit import QWebPage, QWebView

class Scrape(QApplication):
  def __init__(self):
  # only work with ["test"] as it normally takes an array of args
  super(Scrape, self).__init__(["test"])
  # Create a QWebView instance and store it.
  self.webView = QWebView()
  # Connect our searchform method to the searchform signal of this new
  # QWebView.
  self.webView.loadFinished.connect(self.searchForm)

  def load(self, url):
  # In the __init__ we stored a QWebView instance into self.webView so
  # we can load a url into it. It needs a QUrl instance though.
  self.webView.load(QUrl(url))

  def searchForm(self):
  # We landed here because the load is finished. Now, load the root document
  # element. It'll be a QWebElement instance. QWebElement is a QT4.6
  # addition and it allows easier DOM interaction.
  documentElement = self.webView.page().currentFrame().documentElement()
  # Let's find the search input element.
  print("Begin search")
  inputSearch = documentElement.findFirst('id="sharesInIndexTable"')
  # Disconnect ourselves from the signal.
  self.webView.loadFinished.disconnect(self.searchForm)
  print("End search")
  # And connect the next function.
  self.webView.loadFinished.connect(self.searchResults)

  def searchResults(self):
  # As seen above, first grab the root document element and then load all g
  # classed list items.
  print("Begin results")
  results = self.webView.page().currentFrame().documentElement().findAll('td')

  # Change the resulting QWebElementCollection into a list so we can easily
  # iterate over it.
  for e in results.toList():
    # Just print the results.
    print(e.tohtml())
  # We are inside a QT application and need to terminate that properly.
  print("End results")
  self.exit()

# Instantiate our class.
my_scrape = Scrape()
# Load the Google homepage.
my_scrape.load('http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840')
# Start the QT event loop.
my_scrape.exec_()

我添加了 print() 语句来确定程序是否完全执行命令。这根本不会产生任何结果(除了打印语句)

检查源页面,我可以找到填充表格的脚本:

var sharesInIndex =  
load: function () 
var index = webCore.getInstrument();
var nLabel = 'nm';
var hiddenAttributes = ",lists,tp,hlp,isin,note,";
var xslt = "inst_table.xsl";
var options =  ",noflag,sectoridicon,";
var xpath = "//index//instruments";
// Check if swedish r�nteindex or Icelandic r�nteindex.
if ( index.indexOf('OMFSE') >= 0 || webCore.getInstrument().indexOf('IS00000') >= 0 ) 
    hiddenAttributes += ",to,sectid,";
    nLabel = 'fnm';


// Check if weights index present (typeof)
var shbindex = ",SE0002834820,SE0002834838,SE0002834846,SE0002977397,";
if ( shbindex.indexOf(index) >= 0 ) 
    xslt = "inst_table_windex.xsl";
    options += "windex,";
    xpath = "//index";


var query = webCore.createQuery(
    Utils.Constants.marketAction.getIndexInstrument, 
    inst__a: "0,1,2,5,37,4,20,21,23,24,33,34,97,129,98,10", /* 87,*/
    Instrument: index,
    XPath: xpath,
    ext_xslt: xslt,
    ext_xslt_lang: currentLanguage,
    ext_xslt_tableId: "sharesInIndexTable",
    ext_xslt_hiddenattrs: hiddenAttributes,
    ext_xslt_notlabel: nLabel,
    ext_xslt_options: options
  );

  $("#sharesInIndexOutput").empty().loading("/static/nordic/css/img/loading.gif");
  $("#sharesInIndexOutput").load( webCore.getProxyURL('prod'), xmlquery: query,
    function( responseText, textStatus, XMLHttpRequest) 
      $("#sharesInIndexTable").tablesorter(
        widgets: ['zebra'], 
        textExtraction: 'complex', 
        numberFormat: Utils.Constants.numberFormat[currentLanguage]
        );
      $("#sharesInIndexTable a").each( function() 
        $(this).attr("href",webCore.getURL( Utils.Constants.pages.micrositeShare, $(this).attr('name') ));
      );
    );
  
;

$(document).ready( sharesInIndex.load );

我知道有一个“execute_script”命令,但我不知道你如何实现它,也没有找到任何适合这个的例子——我不介意结果是 Json、HTML 还是纯文本。我相信这就是答案所在:(1) 加载页面,(2) 运行页面脚本,(3) 获取结果,(4) 解析/打印/保存结果...

如果有一个无头解决方案,我最好有一个无头解决方案,甚至 Windows 上的 Phantomjs 也不是完全无头的,因为它会弹出一个 cmd 窗口(我知道你可以通过 Linux 上的虚拟显示器摆脱这个 - 但那是不是环境)。另外,只是告诉我:哦,您必须轮询它以查看数据是否已加载,然后检索它不是很有帮助:您能告诉我(即使是伪代码)轮询是如何完成的,更重要的是大致在哪里程序是否会进行轮询(这就是我发布完全可执行代码的原因 - 如果其他人有同样的问题,他们应该有一个完整且易于理解的答案)。

我最近的尝试(1 - 插入延迟以允许 AJAX 加载)

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *
import time

class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)
    self.mainFrame().load(QUrl(url))  
    self.loadFinished.connect(self._loadFinished)   
    self.app.exec_()  

  def _loadFinished(self, result):
    time.sleep(5)
    self.frame = self.currentFrame()  
    self.app.quit()  

url = 'http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840'  
r = Render(url)  
html = r.frame.toHtml()
print(html)

(2 - 轮询源页面中的已知项目) - 使用 firebug 检查器找到的项目 - findFirst 的参数可能语法错误。

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *
import time

class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)
    self.mainFrame().load(QUrl(url))  
    self.loadFinished.connect(self._loadFinished)   
    self.app.exec_()  

  def _loadFinished(self, result):
    counter = 0
    while(self.mainFrame().documentElement().findFirst("id=sharesInIndexTable")):
      counter+=1
      print(counter)
      time.sleep(1)    
    self.frame = self.currentFrame()  
    self.app.quit()  

url = 'http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840'  
r = Render(url)  
html = r.frame.toHtml()
print(html)

最后一个有一个计数器来显示是否有事情发生。它永远计数,必须用 ctrl-c 停止。

(3 - 使用 WebElement 的另一个变体)

import sys  
from PyQt4.QtGui import *  
from PyQt4.QtCore import *  
from PyQt4.QtWebKit import *
import time

class Render(QWebPage):  
  def __init__(self, url):  
    self.app = QApplication(sys.argv)  
    QWebPage.__init__(self)
    self.mainFrame().load(QUrl(url))  
    self.loadFinished.connect(self._loadFinished)   
    self.app.exec_()  

  def _loadFinished(self, result):
    table = self.mainFrame().documentElement().findFirst("id=sharesInIndexTable")
    print(table)    #prints: <PyQt4.QtWebKit.QWebElement object at 0x0319FB0>
    print("Attributes:")
    print(table.attributeNames())    #prints: [] i.e. None 
    print("Classes: ")
    print(table.classes())      #prints: [] i.e. None
    print("InnerXML: " + table.toInnerXml())   #prints nothing
    print("OuterXML: " + table.toOuterXml())   #prints nothing
    print("Done")
    self.frame = self.currentFrame()  
    self.app.quit()  

url = 'http://www.nasdaqomxnordic.com/index/index_info?Instrument=DK0016268840'  
r = Render(url)  
html = r.frame.toHtml()

这个也没有成功。我输入了打印的代码。那里显然有一个物体,但我看不到里面是什么。

【问题讨论】:

【参考方案1】:

我知道这已经很长时间了,但这个答案是为以后遇到类似情况的访问者准备的

我遇到了类似的问题,我尝试了各种方法,例如等待来自 QWebPage 和 QWebFrame 的 loadFinished 信号,等待来自 QWebFrame.intialLayoutCompleted() 的信号等。

最后对我有用的是:

我只是在普通浏览器中呈现页面。检查由于 javascript 而未在 PyQt 中呈现的元素,获取该元素的 id(如果它是一个包含多个元素、表格等的 div,则获取 div id)。现在在 yourPage.loadFinished 函数的 python 代码中调用 yourFrame.evaluateJavaScript("document.getElementById(element_id_retrieved_earlier')")。

这将等待 id 被检索到,然后将等待嵌入的脚本被执行。

【讨论】:

我尝试使用 euronext.com/en/products/equities/NL0000352565-XAMS/… 和 elementID = 'instrument-factsheet' 来实施您的解决方案,但我一定不能正确地执行此操作。您能否发布一些您的方法的代码sn-p。谢谢。

以上是关于用 Python 执行网页的脚本的主要内容,如果未能解决你的问题,请参考以下文章

怎样让webbrowser执行JS脚本正常显示网页

python 脚本被意外打断之后(比如开网页但是断网了)如何从当前工作现场继续运行?

浏览器的网页我想写一个js脚本控制他,怎么让这个别人的网页执行我写好的js脚本?

python爬取网页时会不会加载css,js等内容

如何用python抓取js生成的数据

如何用php作Linux自动执行脚本