从 Python 向 Dotnet Core 的爬虫迁移记录

昨天用 dotnet core 把在 python 下实现的爬虫重写了一遍,体验很好( Linq + HtmlAgilityPack + ScrapySharp ),做一些零碎的记录

轮子的选择

以下涉及到的库均可以使用 Nuget 安装

HTML 爬取库

如果遇到没有 DDOS 防护的静态页面,在 python 下用 urllib 库发送请求即可获取网页代码

// ...
req = request.Request(url, headers=HEAD)
response = request.urlopen(req)
html = response.read().decode('utf-8')
// ...

在 dotnet 下同样可以使用自带的 WebClient、WebRequest 实现,但既然使用了 ScrapySharp 这个解析库(原因见下文),这里也可以使用 ScrapySharp 自带的 Web Client 实现

using ScrapySharp.Network;
// ...
var browser = new ScrapingBrowser();
WebPage page = browser.NavigateToPage(new Uri("your url here"));
var html = page.Content;
// ...

除此之外,python 下万能的浏览器自动化工具 selenium 也同样支持 dotnet core,区别在于一些 Options 的添加

在 python 下(使用 Chrome)

chrome_options = Options()
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
prefs = {'profile.managed_default_content_settings.images': 2} # 不加载图片
chrome_options.add_experimental_option('prefs', prefs)
browser = webdriver.Chrome(options=chrome_options)

在 dotnet core 下,同样的打开方式变成了

var chromeOptions = new ChromeOptions();
chromeOptions.AddArgument("--no-sandbox");
chromeOptions.AddArgument("--disable-dev-shm-usage");
chromeOptions.AddUserProfilePreference("profile.managed_default_content_settings.images", 2); //这里
var mychrome = new ChromeDriver(chromeOptions);

HTML 解析库

对于 python 下常用的 html 解析库 BeautifulSoup4,在 dotnet core 下我选择了 HtmlAgilityPack

在 python 下,我通常使用以下方法将获取到的 html 源代码解析为 lxml (或其他)格式

# ...
soup = BeautifulSoup(html, 'lxml')
# ...

现在在 dotnet core 下,变成了

using HtmlAgilityPack;

// ...
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
///
htmlDoc.Load("/your/path/here/pageSource.html");
从文件中加载
///
// ...

HAP 默认使用 XPath 语法(在 XML 文档中查找信息的语言),且暂不支持 CSS 选择器

例如在 python 中,我通常以下面的手法筛选信息

# ...
result = soup.select("div.xxxclass")
# ...

因为懒得学习 XPath想尽快尝试 dotnet core 的爬虫,ScrapySharp 出现了,此框架是对 HtmlAgilityPack 的一次扩展,添加了其缺少的 CSS 选择器,并且自带一个 Web Client 用来获取网页代码

安装之后就可以使用 CssSelect 方法筛选网页代码

using ScrapySharp.Extensions;
// ...
var result = htmlDoc.DocumentNode.CssSelect("table tr.app");
// ...

稍微熟悉 XPath 语法后,也可以很方便提取信息,如下(单个节点中的信息):

using HtmlAgilityPack;
// ...
string subID = tds[1].SelectSingleNode(".//a[@href]").Attributes["href"].Value.Split('/')[2];
string name = tds[1].SelectSingleNode(".//b").InnerText;
string URL = tds[0].SelectSingleNode(".//a[@href]").Attributes["href"].Value.Split('?')[0];
// ...

System.Linq 的使用

在 python 下使用 CSS 选择器筛选后的数据类型通常为 List,可以直接通过数组操作获取目标信息

在 dotnet core 下,因为 HtmlAgilityPack 的返回数据类型不同,CSS 筛选过后的数据类型无法直接以 List 类型对待,此时经典的 Linq 登场

using System.Linq;
// ...
var tds = each.CssSelect("td").ToArray();
// ...
var name = tmpDoc.DocumentNode.CssSelect("div.apphub_AppName").ToArray();
// ...

Json的读写

关于 dotnet core 下 json 文件的读写,.NET Core 读写json 发送邮件 中已有涉及,为了牢记复读一次

有时我需要以 json 格式保存爬取的数据,或者加载一些以 json 格式存储的配置信息,python 下有自带的 json 库满足需求,dotnet core 下可以使用 Newtonsoft.Json

读取 json

using System.IO;
using Newtonsoft.Json;

public T LoadData(string path) {
    var content = File.ReadAllText(path);
    return JsonConvert.DeserializeObject<T>(content);
}

写入 json

using System.IO;
using Newtonsoft.Json;

public void WriteData(T data, string path) {
    string json = JsonConvert.SerializeObject(data, Formatting.Indented);
    File.WriteAllText(path, json);
}

Telegram Bot

有时我需要把爬取到的信息推送给各个设备,在 python 下我使用 pyTelegramBotAPI 库发送消息,dotnet core 同样有 Telegram Bot 的框架 Telegram.Bot

如果只是发送消息,可以不用监听获取用户发来的消息

using Telegram.Bot;
using Telegram.Bot.Types.Enums;

public async void SendMessage(string chatId, string msg, bool htmlMode = false) {
    if (msg != string.Empty) {
        await BotClient.SendTextMessageAsync(
            chatId: chatId,
            text: msg,
            parseMode: htmlMode ? ParseMode.Html : ParseMode.Default
        );
    }
}

由于其使用了异步方式发送信息,需要添加 async/await 关键字

2020.04.28更新:

ScrapingBrowser 的编码问题

由于 ScrapySharp 中的 ScrapingBrowser 无法自动识别网页的编码(GBK、UTF-8等等),对于网页中的特殊 Unicode 字符会返回 ???,无法识别

因此需要手动设定 ScrapingBrowser 的 Encoding 属性:

using System.Text; //Encoding.UTF8

// ...
var browser = new ScrapingBrowser() { Encoding = Encoding.UTF8 };
WebPage page = browser.NavigateToPage(new Uri(URL));
// ...

效果如图,原来无法正常获取的商标符号 ™ 可以正常显示了

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据