Selenium爬取网页实战

Selenium简介

Selenium是一个浏览器自动测试框架,可以通过模拟各种浏览器行为完成各种测试任务。但它能做的远不止这些,一个更实用的功能是作网页内容爬取。

有人会问为什么不直接使用curl、python的urllib或C#的HttpClient完成这样的功能,因为后者不容易爬取许多需要javascript渲染的内容。二者的本质区别在于,Selenium是通过一个真实的浏览器(webdriver)行为获取网页内容,而curl类通过http请求获取网页字符串,而不对该字符串作任何解析处理。理论上来说,只要通过浏览器查看源代码能看到的所有html的内容,都可以通过Selenium爬取下来。

但并非Selenium作为爬虫就一定优于curl,虽然它应用场景更广泛,但由于模拟了浏览器行为,需要解析执行script等一系列操作,导致它的执行过程更繁琐,所占系统资源较多,同时爬取的速度显著慢于curl。因此,实际应用中要分情况选用不同的方式,对于静态html内容的爬取,还是使用curl类的方案效率更高,而对于使用script进行反爬的各种页面,可以通过Selenium比较方便地获取网页内容。

实战

本文并非一个完整的Selenium教程,出于实用的目的,把一些新手常见的需求列出来供参考。最常见的需求即是给定一个URL,获取其网页内容。本节以C#为例进行演示。

安装Selenium

C#中安装Selenium非常简单,需要通过nuget包管理器搜索"selenium"安装两个包:

  • Selenium.WebDriver: Selenium基础包。
  • Selenium.Chrome.WebDriver: ChromeDriver,安装后不必自行去Google下载ChromeDriver,即装即用。

获取网页html

最为普遍的需求,通过driver.Title和driver.PageSource获取网页标题和html内容,实现也非常简单:

using (var driver = new ChromeDriver())
{
    driver.Navigate().GoToUrl("http://www.baidu.com");
    var title = driver.Title;
    var html = driver.PageSource;
}

设置需要用户名密码验证的代理

定制ChromeDriver基本都通过ChromeOptions()类完成。

使用代理需要设置ChromOptions中的Proxy成员变量。ChromeDriver设置需要验证的代理有不少文章,但许多测试并不可用。亲测可用的是如下方法(大括号中的内容自行替换):

var proxy = new Proxy();
proxy.HttpProxy = "http://{username}:{password}@{proxyserver}:{port}";
var options = new ChromeOptions();
options.Proxy = proxy;

using (var driver = new ChromeDriver(options))
{
...
}

不启动Chrome窗口

执行程序时每次会启动一个新的浏览器窗口展示程序定义的各种行为,但作为爬虫其实并不需要这样的前端:

var options = new ChromeOptions();
options.AddArgument("headless");

关闭ChromeDriver控制台

Windows下的ChromeDriver每次还会启动控制台,输出一些对应的log,搜索了下,居然有人说是需要通过修改源码实现...当然用不着这么复杂:

var driverService = ChromeDriverService.CreateDefaultService();
driverService.HideCommandPromptWindow = true;

using (var driver = new ChromeDriver(driverService, options))
{
...
}

设置ChromeDriver路径

默认ChromeDriver会在当前bin目录查找,也可通过更新Selenium.Chrome.WebDriver包完成,只是有时nuget更新得没那么及时,需要手动下载ChromeDriver到本地路径,放bin目录每次rebuild会被覆盖,不方便,此时就需要手动设置ChromeDriver路径。

ChromeDriver类本身有三个带路径的构造函数:

public ChromeDriver(string chromeDriverDirectory);
public ChromeDriver(string chromeDriverDirectory, ChromeOptions options);
public ChromeDriver(string chromeDriverDirectory, ChromeOptions options, TimeSpan commandTimeout);
上面三个用法不必赘述,此外,还有这个构造函数:
public ChromeDriver(ChromeDriverService service, ChromeOptions options);

可在创建ChromeDriverService时设置:

var driverService = ChromeDriverService.CreateDefaultService(@"c:\localdriverpath\");
driverService.HideCommandPromptWindow = true;

using (var driver = new ChromeDriver(driverService, options))
{
   ...
}

完整代码

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new Proxy();
            proxy.HttpProxy = "http://{username}:{password}@{proxyserver}:{port}";

            var options = new ChromeOptions();
            options.Proxy = proxy;
            options.AddArgument("headless");

            var driverService = ChromeDriverService.CreateDefaultService();
            driverService.HideCommandPromptWindow = true;

            using (var driver = new ChromeDriver(driverService, options))
            {
                driver.Navigate().GoToUrl("http://www.baidu.com");
                var title = driver.Title;
                var html = driver.PageSource;

                Console.WriteLine($"Title: {title}");
                Console.WriteLine(html);
            }
            Console.ReadKey();
        }
    }
}

References

Selenium

Most Complete Selenium WebDriver C# Cheat Sheet