coderLMN / AutomatedDataCollectionWithR

《基于 R 语言的自动化数据采集技术》读者讨论区
28 stars 10 forks source link

请问怎样从腾讯新闻的评论页提取评论信息呢? #2

Open jlhnihao opened 8 years ago

jlhnihao commented 8 years ago

您好,我从9.1.9节看到从实时DOM树中提取底层的HTML代码,于是想在本节的框架下提取腾讯新闻的评论,但好像page_source()函数并不获得含有评论内容的DOM树。在其他章节我没找到如何解决该问题的线索,特向您请教。如果需要的话,不如我们就以最近闹得沸沸扬扬的雷洋的新闻事件的评论为例,我附上评论网址:http://coral.qq.com/1398070563 。希望能得到您的帮助,非常感谢。

coderLMN commented 8 years ago

谢谢你提出了一个非常有价值的问题。

我看了一下,腾讯新闻的网页把评论内容和控制按钮都放在了一个 id 为 commentIframe 的 iFrame 里,所以直接用

> loadMoreButton <- element_xpath_find(value = '//*[@id="loadMore"]/span')

这样的语句是找不到那个“加载更多”按钮的,只会得到一个空对象,再用

> element_click(loadMoreButton)

点击不到那个按钮,也就不会有点击的效果。

其实这种情况在微博里也存在。你提的这个问题对于国内很多网站有代表性,而原书中并没有分析这种问题的解决办法。

顺带说一句,其实 iFrame 有很多弊端,W3C 早就不推荐使用了,不理解为何国内这么多网站还在用,可能是因为它们用的某些框架或者库内部就是这样实现的吧。

按照 9.1.9 节讲解的方法确实解决不了这个问题。里面用到的 Rwebdriver 组件是该书的作者之一 C. Rubba 编写的,我个人觉得它做得并不好,在它的文档里也没找到如何处理 iFrame 的功能。后来我改用 RSelenium 组件,很轻松地解决了 iFrame 问题。它的文档可以参阅:https://cran.r-project.org/web/packages/RSelenium/vignettes/RSelenium-basics.html

解决这个问题的总体思路就是,先找到 “加载更多” 评论那个按钮所在的 iFrame,然后把当前 DOM 切换到这个 iFrame,再从中去定位该按钮。获得这个按钮之后,可以用一个 while 循环重复点击动作,直到所有评论加载完成、按钮失效为止,这种情况下就说明所有评论都已成功加载到 DOM 里了。

我自己尝试的代码是这样的:

> install.packages("RSelenium")  
> library(RSelenium)             
> library(XML)                      
> remDr <- remoteDriver(remoteServerAddr = "localhost" , port = 4444, browserName = "firefox" )
> remDr$open()
> remDr$navigate('http://coral.qq.com/1398070563/')
> iFrame <- remDr$findElement(using = 'xpath', "//*[@id = 'commentIframe']")   # 评论所在的 iFrame
> remDr$switchToFrame(iFrame)     # 先切换到评论 iFrame
> loadMoreButton <- remDr$findElement(using = 'xpath', "//*[@id = 'loadMore']/span")    # 在评论 iFrame 里找到那个 “加载更多” 按钮
> while(exists("loadMoreButton")) {loadMoreButton$clickElement() ;  loadMoreButton <- remDr$findElement(using = 'xpath', "//*[@id = 'loadMore']/span") }     # 在评论 iFrame 里循环点击 “加载更多” 按钮,直到评论加载完该按钮不再出现为止(需要等待的时间比较长)
> comments <- remDr$findElement(using = 'xpath', '//*[@id="allComments"]')   # 获取的所有评论就在 comments 里。这是一个大 div ,评论信息还需要进一步提取,这个就留给你继续吧

以上代码我在自己的环境下测试通过(MacBook Pro, OSX 10.11.4,JDK 8, Selenium 2.53.0 , R 3.3.0, IDE 是 RStudio),你可以试试,有问题再告诉我。

============================= 高级班的分隔线 ==================================

其实这个问题对于掌握了 JavaScript 语言的读者来说就非常简单了,因为 JS 可以很方便地操作 DOM,触发事件。

这里的思路和上面是一样的,但是不需要任何库、插件、组件之类,连 R 语言和 Selenium 都不需要,只要有个浏览器即可,简单得不能再简单了。以下代码可以在加载了这个新闻的页面上右键点击“检查元素”(Inspect Element),在“控制台”(Console )中输入并执行:

var iframe = document.getElementById('commentIframe');  // 找到评论所在的 iFrame
var innerDoc = iframe.contentDocument || iframe.contentWindow.document;  // 定位到 iFrame 的DOM
var event = document.createEvent("HTMLEvents");
event.initEvent("click", false, false);  // 给这个按钮初始化一个点击事件
var allComments, t, loadMore;  
function readComments(){
    loadMore = innerDoc.getElementById('loadMore').children[0];  // 等待 DOM 加载完成,重新读取这个按钮
    if(loadMore.nextElementSibling.style.display != 'block')  {   // 隐藏的“没有更多评论” 提示文字不显示,说明评论尚未全部加载
        loadMore.dispatchEvent(event);   // 触发点击事件
    }
    else{
        clearInterval(t);   // 按钮已经不显示,说明加载完成,可以清除循环
        console.log('Done!');    // 在控制台显示任务完成的提示
        allComments = innerDoc.getElementById('allComments');   // 所有评论都在这里了
    }  
}
t = setInterval("readComments()",10000);   // 每次点击后先等待 10 秒,让新评论加载进来。

执行完这些代码后,所有的评论都在 allComments 对象里,后续就可以对它进行分解和分析。

正如我在译者序里所说的,本书用 R 语言作为代码演示的平台,但是书中讲解的方法并不局限于 R 语言,在很多语言平台上都是很容易实现的。读者也可以用自己熟悉的语言来尝试一下,这样才是学以致用嘛。

jlhnihao commented 8 years ago

谢谢您抽出宝贵的时间来解答,这几天因忙于它事未能及时回复,还请海涵。 循着您介绍的思路,把网页Frame、iframe,以及RSelenium包的介绍读了几遍,很受益。读完自己先尝试做了一下,最后回头看了您提供的代码。 前面写的跟您提供的代码类似,只是while后面有点不同,但基本思路是跟您一致的。我贴在下面:

while(loadMoreButton$getElementText()[[1]]=="加载更多") {
    loadMoreButton$clickElement()
    loadMoreButton <- remDr$findElement(using = 'xpath', "//*[@id = 'loadMore']/span")
}
iframe_parsed<-htmlParse(remDr$getPageSource()[[1]],encoding="UTF-8")
iframe_value<-xpathSApply(iframe_parsed,"//li//div[@class='np-post-content']/p",xmlValue)

因为发现点击“加载更多”点到最后是“没有更多了”,但该span仍然存在,也就是exists("loadMoreButton")依然是TRUE,这样就会继续往下循环,系统会报错:“Summary: UnknownError...”,但仍能正常出结果。所以这个地方修改了一下。 后面两句解析后,运用本书前面所讲的内容,就不存在从大div里面再进行提取数据的操作了。 我的运行环境是win7系统下R Console(64-bit 3.3.0),基本上就在里面直接写了。 您讲的运用JavaScript,对我启发也很大(虽然还没学过,有时间补上这一块。) 再次感谢您指点。

sportzhang commented 8 years ago

老师好,我是一名学生,正在学习您的书,上面您的代码中remDr <- remoteDriver(remoteServerAddr = "localhost" , port = 4444, browserName = "firefox" )我在WINDOWS系统上操作,运行remDr$open()就出错:Error in queryRD(paste0(serverURL, "/session"), "POST", qdata = toJSON(serverOpts)) :,纠结了一天,试了好些地方没找到原因,希望两位老师解答一下,谢谢老师! @jlhnihao @coderLMN

coderLMN commented 8 years ago

这个错误很有可能是因为你没有成功启动 Selenium ,可以看一下 Selenium 启动界面里是否有错误信息。

coderLMN commented 8 years ago

出错了要看错误信息啊:

Error: Unable to access jarfile selenium-server-standalone-2.53.0.jar

这个信息说的就是访问不到你的 Selenium jar 文件。从你的命令看: C:>java -jar selenium-server-standalone-2.53.0.jar

所以 java 会在你的 C 盘当前目录下找这个 jar 文件,如果这个文件不在那里,那你就要给出具体的路径才行。

sportzhang commented 8 years ago

@老师,前面那个错误变成下面这样,好像就是上面jlhnihao 说的 `> remDr<-remoteDriver(remoteServerAddr = "localhost" , port = 4444, browserName = "firefox",platform="WINDOWS")

remDr$open() [1] "Connecting to remote server" Error:Summary: UnknownError Detail: An unknown server-side error occurred while processing the command. class: org.openqa.selenium.WebDriverException` jlhnihao 说的方式我也试了,改了改也不行!selenium也启动了。再次来请教您!谢谢您的耐心指导! @coderLMN @jlhnihao

coderLMN commented 8 years ago

这个错误应该是 Selenium 服务器无法启动 firefox 浏览器,有可能有两个原因:1. 有其它程序占用了 4444 端口,或者 java 的版本太低,导致你的 Selenium 服务器并没有正常启动。可以去看一下启动的日志是否有报错; 2. firefox 浏览器的路径没有保存到系统路径里,所以找不到。可以试试重新安装 firefox 。

sportzhang commented 8 years ago

老师,我Java已经是最新的,Firefox又重新安装到系统路径,可还是不行! > remDr$open() [1] "Connecting to remote server" Error: Summary: UnknownError Detail: An unknown server-side error occurred while processing the command. class: java.lang.IllegalArgumentException 是不是系统不行啊!我的是WINDOWS 10 remDr<-remoteDriver(remoteServerAddr="localhost",port = 4444,browserName="Firefox",platform="WINDOWS 10")

coderLMN commented 8 years ago

你能把 Selenium 启动时输出的内容贴过来吗?我怀疑还是 Selenium 的启动有问题。

sportzhang commented 8 years ago

这是在系统命令行运行的结果,老师您看一下! C:\Users\zhang>java -jar selenium-server-standalone-2.53.0.jar 13:58:31.340 INFO - Launching a standalone Selenium Server 13:58:31.447 INFO - Java: Oracle Corporation 25.65-b01 13:58:31.449 INFO - OS: Windows 10 10.0 amd64 13:58:31.479 INFO - v2.53.0, with Core v2.53.0. Built from revision 35ae25b 13:58:31.568 INFO - Driver class not found: com.opera.core.systems.OperaDriver 13:58:31.571 INFO - Driver provider com.opera.core.systems.OperaDriver is not registered 13:58:31.589 INFO - Driver provider org.openqa.selenium.safari.SafariDriver registration is skipped: registration capabilities Capabilities [{browserName=safari, version=, platform=MAC}] does not match the current platform WIN10 13:58:31.598 INFO - Driver class not found: org.openqa.selenium.htmlunit.HtmlUnitDriver 13:58:31.604 INFO - Driver provider org.openqa.selenium.htmlunit.HtmlUnitDriver is not registered 13:58:31.763 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub 13:58:31.766 INFO - Selenium Server is up and running

sportzhang commented 8 years ago

在R里运行前面两行就出现下面的错误: `> remDr<-remoteDriver(remoteServerAddr="localhost",port = 4444,browserName="Firefox",platform="WINDOWS 10")

remDr$open() [1] "Connecting to remote server" Error: Summary: UnknownError Detail: An unknown server-side error occurred while processing the command. class: java.lang.IllegalArgumentException`

coderLMN commented 8 years ago

我手头没有 windows 10 的电脑,没法重现。不过,

platform="WINDOWS 10"

这个参数不是必需的,也不一定正确,可以去掉,再试试看。

sportzhang commented 8 years ago

老师,好像那个参数不重要,去掉也不行,系统命令行我选了一些信息,您看一下! Caused by: org.openqa.selenium.WebDriverException: The best matching driver provider org.openqa.selenium.ie.InternetExplorerDriver can't create a new driver instance for Capabilities [{nativeEvents=true, browserName=Firefox, javascriptEnabled=true, version=, platform=ANY}] java.util.concurrent.ExecutionException: org.openqa.selenium.WebDriverException: The best matching driver provider org.openqa.selenium.ie.InternetExplorerDriver can't create a new driver instance for Capabilities [{nativeEvents=true, browserName=Firefox, javascriptEnabled=true, version=, platform=ANY}] Build info: version: '2.53.0', revision: '35ae25b', time: '2016-03-15 17:00:58' System info: host: 'zhang-PC', ip: '192.168.0.106', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_65' Driver info: driver.version: unknown

coderLMN commented 8 years ago

我已经重现了你这个问题。

在你的命令

> remDr<-remoteDriver(remoteServerAddr="localhost",port = 4444,browserName="Firefox")

里面,browserName="Firefox" 里的 firefox 第一个 f 不能是大写,否则就会出这个错误:

Error: Summary: UnknownError Detail: An unknown server-side error occurred while processing the command. class: org.openqa.selenium.WebDriverException

你把 F 改成小写再试试。

LeeWill2016 commented 8 years ago

@coderLMN @KAIKAIZHANG 你们好, 我与@KAIKAIZHANG 遇到的情况类似----没有成功启动chrome浏览器,不知道哪里没设置好 以下是使用的过程反馈: Selenium 版本:selenium-server-standalone-2.53.0.jar; W7系统;电脑已安装chrome浏览器;java版本7.0.650 第一步打开Selenium 1

第二步:回到R的控制台执行代码:

library(RSelenium) library(XML) remDr <- remoteDriver(remoteServerAddr = "localhost" , port = 4444, browserName = "chrome",platform="WINDOWS" ) remDr$open() [1] "Connecting to remote server" Error: Summary: UnknownError Detail: An unknown server-side error occurred while processing the command. class: java.lang.IllegalStateException

Selenium执行结果: 2 3

urdaddy85 commented 8 years ago

@LeeWill2016 ,stackoverflow这里有2个贴子,跟启用Chrome有关的,可以看一下: http://stackoverflow.com/questions/33124857/rselenium-is-not-working http://stackoverflow.com/questions/31124702/rselenium-unknownerror-java-lang-illegalstateexception-with-google-chrome

如果需要启用Chrome,需要先下载ChromeDriver.exe, 然后在环境变量中设置这个文件的路径。

urdaddy85 commented 8 years ago

@coderLMN 您好,在运行RSelenium的使用报错,google了几天,仍然没有解决,希望指点一下:

require(RSelenium)
# 切换selenium-server-standalone.jar 的路径
setwd("D:\\Program Files\\R\\R-3.3.1\\library\\RSelenium\\bin") 
startServer()
remDr <- remoteDriver(remoteServerAddr = "localhost" 
                      , port = 4444
                      , browserName = "firefox"
)
remDr$open()

# 运行remDr$open()报错
[1] "Connecting to remote server"
Error:   Summary: UnknownError
     Detail: An unknown server-side error occurred while processing the command.
     class: org.openqa.selenium.WebDriverException

# R的基本信息如下,且JAVA也已经更新到最新的版本
                      platform                           arch                             os 
          "x86_64-w64-mingw32"                       "x86_64"                      "mingw32" 
                        system                         status                          major 
             "x86_64, mingw32"                             ""                            "3" 
                         minor                           year                          month 
                         "3.1"                         "2016"                           "06" 
                           day                        svn rev                       language 
                          "21"                        "70800"                            "R" 
                version.string                       nickname 
"R version 3.3.1 (2016-06-21)"             "Bug in Your Hair" 
coderLMN commented 8 years ago

@LeeWill2016 : @urdaddy85 说得对,缺省支持的是 firefox,其他浏览器需要安装驱动。

coderLMN commented 8 years ago

@urdaddy85 你的操作系统 mingw32 我不了解,会不会是因为这个操作系统的问题?你是否可以换个其他操作系统试试看?

urdaddy85 commented 8 years ago

@coderLMN 感谢回复。我用的是WIN10的操作系统。最终用了PhantomJS去链接服务器。

urdaddy85 commented 8 years ago

问题解决:

  1. RSelenium默认浏览器的打开路径一定是在C盘program files里面的,所以firefox最好默认安装路径;
  2. Selenium 2.53 跟 firefox 47兼容不好,需要下载这个驱动Marionette driver (https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver) 要不就使用firefox 46版本(参考:http://stackoverflow.com/questions/37693106/selenium-2-53-not-working-on-firefox-47)。