使用Selenium来抓取动态加载的页面
使用Selenium来抓取动态加载的页面
黄亿华 发表于4年前
使用Selenium来抓取动态加载的页面
  • 发表于 4年前
  • 阅读 47319
  • 收藏 94
  • 点赞 22
  • 评论 31

330元/年抢阿里云香港云服务器,节省80%出海成本 >>>   

一般的爬虫都是直接使用http协议,下载指定url的html内容,并对内容进行分析和抽取。在我写的爬虫框架webmagic里也使用了HttpClient来完成这样的任务。

但是有些页面是通过js以及ajax动态加载的,例如:花瓣网。这时如果我们直接分析原始页面的html,是得不到有效的信息的。当然,因为无论怎样动态加载,基础信息总归是包含在初始页面中得,所以我们可以用爬虫代码来模拟js代码,js读取页面元素值,我们也读取页面元素值;js发送ajax,我们就拼凑参数、发送ajax并解析返回的json。这样总归是能做的,但是比较麻烦,有没有比较省力的方法呢?比较好的方法大概是内嵌一个浏览器了。

Selenium是一个模拟浏览器,进行自动化测试的工具,它提供一组API可以与真实的浏览器内核交互。Selenium是跨语言的,有Java、C#、python等版本,并且支持多种浏览器,chrome、firefox以及IE都支持。

在Java项目中使用Selenium,需要做两件事:

  • 在项目中引入Selenium的Java模块,以Maven为例:

      <dependency>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
          <version>2.33.0</version>
      </dependency>
    
  • 下载对应的driver,以chrome为例:http://code.google.com/p/chromedriver/downloads/list

    下载后,需要将driver的位置写到Java的环境变量里,例如我在mac下将其下载到了/Users/yihua/Downloads/chromedriver,则需要在程序里添加以下代码(当然在JVM参数里写-Dxxx=xxx也是可以的):

    <!-- lang: java --> System.getProperties().setProperty("webdriver.chrome.driver","/Users/yihua/Downloads/chromedriver");

Selenium的API挺简单的,核心是WebDriver,下面是动态渲染页面,并获取最终html的代码:

<!-- lang: java -->
	 @Test
    public void testSelenium() {
        System.getProperties().setProperty("webdriver.chrome.driver", "/Users/yihua/Downloads/chromedriver");
        WebDriver webDriver = new ChromeDriver();
        webDriver.get("http://huaban.com/");
        WebElement webElement = webDriver.findElement(By.xpath("/html"));
        System.out.println(webElement.getAttribute("outerHTML"));
        webDriver.close();
    }

值得注意的是,每次new ChromeDriver(),Selenium都会建立一个Chrome进程,并使用一个随机端口在Java中与chrome进程进行通信来交互。由此可见有两个问题:

  • 因此如果直接关闭Java程序,Chrome进程可能是无法关闭的。这里需要显示的调用webDriver.close()来关闭进程。

  • 创建进程的开销还是比较大的,尽量对webDriver进行复用会比较好。可惜根据官方的文档,webDriver不是线程安全的,所以我们需要建立一个webDriver池来保存它们。不清楚Selenium是否有这样的接口,反正我是自己写了一个WebDriverPool来完成这个任务。

我已经将Selenium整合到了我的爬虫框架webmagic中,目前还是试用版本,有兴趣的可以一起学习交流。

最后说说效率问题。嵌入浏览器之后,不但要多花CPU去渲染页面,还要下载页面附加的资源。似乎单个webDriver中的静态资源是有缓存的,初始化之后,访问速度会加快。我试用ChromeDriver加载了100次花瓣的首页(http://huaban.com/),共耗时263秒,平均每个页面2.6秒。

为了测试效果,我写了一个花瓣抽取器,抽取花瓣网的分享图片url,用了咱自己的webmagic框架,集成了Selenium。

<!-- lang: java -->
    /**
 * 花瓣网抽取器。<br>
 * 使用Selenium做页面动态渲染。<br>
 */
public class HuabanProcessor implements PageProcessor {

    private Site site;

    @Override
    public void process(Page page) {
        page.addTargetRequests(page.getHtml().links().regex("http://huaban\\.com/.*").all());
        if (page.getUrl().toString().contains("pins")) {
            page.putField("img", page.getHtml().xpath("//div[@id='pin_img']/img/@src").toString());
        } else {
            page.getResultItems().setSkip(true);
        }
    }

    @Override
    public Site getSite() {
        if (site == null) {
            site = Site.me().setDomain("huaban.com").addStartUrl("http://huaban.com/").setSleepTime(1000);
        }
        return site;
    }

    public static void main(String[] args) {
        Spider.create(new HuabanProcessor()).thread(5)
                .scheduler(new RedisScheduler("localhost"))
                .pipeline(new FilePipeline("/data/webmagic/test/"))
                .downloader(new SeleniumDownloader("/Users/yihua/Downloads/chromedriver"))
                .run();
    }
}

sample地址:HuabanProcessor.java

共有 人打赏支持
黄亿华
粉丝 2153
博文 130
码字总数 115979
作品 7
评论 (31)
th小米粥
mark
黄亿华
关掉WebDriver的图片下载功能,应该可以节约不少时间,可惜尝试了很多次都没有成功,怀疑是Mac下chrome28的问题。改天再试吧。最相近的文章:http://blog.csdn.net/lizeyang/article/details/9394067 mac下的chrome配置保存在~/Library/Application\ Support/Google/Chrome/Default/目录下。
傅小黑
原来用selenium觉得好爽的。。现在不喜欢了
改喜欢phantomjs了
黄亿华

引用来自“傅小黑”的评论

原来用selenium觉得好爽的。。现在不喜欢了
改喜欢phantomjs了

phantomjs听说挺久了,感觉高级一点,不过好像只支持js? Selenium不仅需要driver,还要安装对应浏览器,感觉挺麻烦的。
傅小黑

引用来自“黄亿华”的评论

引用来自“傅小黑”的评论

原来用selenium觉得好爽的。。现在不喜欢了
改喜欢phantomjs了

phantomjs听说挺久了,感觉高级一点,不过好像只支持js? Selenium不仅需要driver,还要安装对应浏览器,感觉挺麻烦的。

你可以用phantomjs做无界面浏览器用啊,google搜ghostdriver
黄亿华
发现模拟特别慢的网页的时候,可能没等到页面动态渲染完成就返回了,又加上了一个参数SeleniumDownloader.setSleepTime()
eonezhang
不错
wantsor
博主您的webmagic我已经下载下来学习了源码并试用过了,感觉思路很清晰,解耦也非常到位!
这里想请教一个问题,用SeleniumDownloader的目的是为了解决网页中的AJAX加载部分的数据抓取,这里还有一种情况就是页面一些地方需要点击按钮才会AJAX渲染的,那部分数据用Selenium自带的方法应该能模拟点击去加载,但是封装到webmagic以后,无法调用Selenium自身的API,我如何去模拟点击等事件呢?
黄亿华

引用来自“wantsor”的评论

博主您的webmagic我已经下载下来学习了源码并试用过了,感觉思路很清晰,解耦也非常到位!
这里想请教一个问题,用SeleniumDownloader的目的是为了解决网页中的AJAX加载部分的数据抓取,这里还有一种情况就是页面一些地方需要点击按钮才会AJAX渲染的,那部分数据用Selenium自带的方法应该能模拟点击去加载,但是封装到webmagic以后,无法调用Selenium自身的API,我如何去模拟点击等事件呢?

很抱歉最近才看到!这个问题我也考虑过,但是实现起来还是满复杂的,所以就搁置了。我能想到有两种方案:一种是对Download做定制,直接将按钮逻辑写进去,这样的坏处就是每个站点都要实现一套Downloader。另一种是对PageProcessor做扩展,用它来描述浏览器行为,但是相应的,Downloader和PageProcessor的分隔就没有那么清晰了,因为PageProcessor对Downloader的结果起到反馈。我倒是比较喜欢第一种做法,简单清晰。
wantsor

引用来自“黄亿华”的评论

引用来自“wantsor”的评论

博主您的webmagic我已经下载下来学习了源码并试用过了,感觉思路很清晰,解耦也非常到位!
这里想请教一个问题,用SeleniumDownloader的目的是为了解决网页中的AJAX加载部分的数据抓取,这里还有一种情况就是页面一些地方需要点击按钮才会AJAX渲染的,那部分数据用Selenium自带的方法应该能模拟点击去加载,但是封装到webmagic以后,无法调用Selenium自身的API,我如何去模拟点击等事件呢?

很抱歉最近才看到!这个问题我也考虑过,但是实现起来还是满复杂的,所以就搁置了。我能想到有两种方案:一种是对Download做定制,直接将按钮逻辑写进去,这样的坏处就是每个站点都要实现一套Downloader。另一种是对PageProcessor做扩展,用它来描述浏览器行为,但是相应的,Downloader和PageProcessor的分隔就没有那么清晰了,因为PageProcessor对Downloader的结果起到反馈。我倒是比较喜欢第一种做法,简单清晰。

感谢博主的回复,我前段时间已经用第一种做法实现了一些比如点击按钮才ajax渲染出来的的数据抓取了,自己定制Downloader,还是非常好用,谢谢哈~
FangAlbert
测试了一下0.4.1里面的Selenium,还挺好使,但想问一下博主AJAX页面上的分页怎么处理?例如,这个网站:http://stockdata.stock.hexun.com/gszl/jbgk.aspx,第一页的内容可以抓下来,但是由于表格和页码是JS生成的,我想抓第2页,这个怎么实现(第2页和第1页的Url相同)?
黄亿华

引用来自“FangAlbert”的评论

测试了一下0.4.1里面的Selenium,还挺好使,但想问一下博主AJAX页面上的分页怎么处理?例如,这个网站:http://stockdata.stock.hexun.com/gszl/jbgk.aspx,第一页的内容可以抓下来,但是由于表格和页码是JS生成的,我想抓第2页,这个怎么实现(第2页和第1页的Url相同)?

这个真没考虑过,这么复杂的情况,我宁愿直接模拟ajax请求了...看了一下,请求地址好像是这个?http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=20&titType=null&page=1&callback=hxbase_json15
FangAlbert

引用来自“黄亿华”的评论

引用来自“FangAlbert”的评论

测试了一下0.4.1里面的Selenium,还挺好使,但想问一下博主AJAX页面上的分页怎么处理?例如,这个网站:http://stockdata.stock.hexun.com/gszl/jbgk.aspx,第一页的内容可以抓下来,但是由于表格和页码是JS生成的,我想抓第2页,这个怎么实现(第2页和第1页的Url相同)?

这个真没考虑过,这么复杂的情况,我宁愿直接模拟ajax请求了...看了一下,请求地址好像是这个?http://stockdata.stock.hexun.com/gszl/data/jsondata/jbgk.ashx?count=20&titType=null&page=1&callback=hxbase_json15

好的,多谢,我试试看
wjqburning
请问一下Selenium是不是一定要图形的界面的?如果linux服务器不开X window那不是不能用了吗???
thefun

引用来自“wantsor”的评论

引用来自“黄亿华”的评论

引用来自“wantsor”的评论

博主您的webmagic我已经下载下来学习了源码并试用过了,感觉思路很清晰,解耦也非常到位!
这里想请教一个问题,用SeleniumDownloader的目的是为了解决网页中的AJAX加载部分的数据抓取,这里还有一种情况就是页面一些地方需要点击按钮才会AJAX渲染的,那部分数据用Selenium自带的方法应该能模拟点击去加载,但是封装到webmagic以后,无法调用Selenium自身的API,我如何去模拟点击等事件呢?

很抱歉最近才看到!这个问题我也考虑过,但是实现起来还是满复杂的,所以就搁置了。我能想到有两种方案:一种是对Download做定制,直接将按钮逻辑写进去,这样的坏处就是每个站点都要实现一套Downloader。另一种是对PageProcessor做扩展,用它来描述浏览器行为,但是相应的,Downloader和PageProcessor的分隔就没有那么清晰了,因为PageProcessor对Downloader的结果起到反馈。我倒是比较喜欢第一种做法,简单清晰。

感谢博主的回复,我前段时间已经用第一种做法实现了一些比如点击按钮才ajax渲染出来的的数据抓取了,自己定制Downloader,还是非常好用,谢谢哈~

怎么做的?有时间介绍一下吗?
wantsor

引用来自“thefun”的评论

引用来自“wantsor”的评论

引用来自“黄亿华”的评论

引用来自“wantsor”的评论

博主您的webmagic我已经下载下来学习了源码并试用过了,感觉思路很清晰,解耦也非常到位!
这里想请教一个问题,用SeleniumDownloader的目的是为了解决网页中的AJAX加载部分的数据抓取,这里还有一种情况就是页面一些地方需要点击按钮才会AJAX渲染的,那部分数据用Selenium自带的方法应该能模拟点击去加载,但是封装到webmagic以后,无法调用Selenium自身的API,我如何去模拟点击等事件呢?

很抱歉最近才看到!这个问题我也考虑过,但是实现起来还是满复杂的,所以就搁置了。我能想到有两种方案:一种是对Download做定制,直接将按钮逻辑写进去,这样的坏处就是每个站点都要实现一套Downloader。另一种是对PageProcessor做扩展,用它来描述浏览器行为,但是相应的,Downloader和PageProcessor的分隔就没有那么清晰了,因为PageProcessor对Downloader的结果起到反馈。我倒是比较喜欢第一种做法,简单清晰。

感谢博主的回复,我前段时间已经用第一种做法实现了一些比如点击按钮才ajax渲染出来的的数据抓取了,自己定制Downloader,还是非常好用,谢谢哈~

怎么做的?有时间介绍一下吗?

就是扩展Downloader ,在下载页面之前执行需要做的比如登陆,按钮点击ajax调用等操作,等ajax返回以后再下载页面
宅男小何
htmlunit很挺方便的
黄亿华

引用来自“宅男小何”的评论

htmlunit很挺方便的

很好,有空去弄弄

沙发迪

引用来自“黄亿华”的评论

引用来自“傅小黑”的评论

原来用selenium觉得好爽的。。现在不喜欢了
改喜欢phantomjs了

phantomjs听说挺久了,感觉高级一点,不过好像只支持js? Selenium不仅需要driver,还要安装对应浏览器,感觉挺麻烦的。

phantomjs本身有问题,他不能动态抓取。最新的1.9.7版本的类似抓取taobao.com中商品的动态页面失败,同样的方法ChromeDriver是可以使用的,有一点不爽的就是要弹出界面。不知道有没有好办法。

llcode

引用来自“黄亿华”的评论

引用来自“傅小黑”的评论

原来用selenium觉得好爽的。。现在不喜欢了
改喜欢phantomjs了

phantomjs听说挺久了,感觉高级一点,不过好像只支持js? Selenium不仅需要driver,还要安装对应浏览器,感觉挺麻烦的。

引用来自“袁迪”的评论

phantomjs本身有问题,他不能动态抓取。最新的1.9.7版本的类似抓取taobao.com中商品的动态页面失败,同样的方法ChromeDriver是可以使用的,有一点不爽的就是要弹出界面。不知道有没有好办法。

那你对弹窗是采用什么方法呢?能解决吗?

×
黄亿华
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: