在 Mma 中的函数 URLFetch 看来,爬取数据,如探囊取物般容易,不需要技术!
背景
- 最近打算用 Mma 做个英文助手(背单词用),爬下了 Google 翻译的很多数据
- 通过身份证查询出生地、手机归属地查询,IP所在地查询,GIS信息查询,查询书籍信息……
- 闲时,会逛逛各种论坛(数学、软件、音乐、读书、问答、摄影、成人……),特别是在摄影和成人论坛中往往会有大量漂亮的套图,于是就想把它们收到硬盘里。
- ……(应该还有吧,以后想到一个补一个)
1、Google 翻译
在众多在线翻译工具中,我最喜欢 Google 翻译。
原因一:其准确性和权威性,在地球村貌似无人可望其项背;
原因二,是主观癖好,我非常反感广告。
从 Google 翻译中爬下的数据:几乎 DictionaryLookup[] 中所有单词的基本信息(主要有:译文,定义,译文频率,同义词,常见词组,例句)。
再结合 WordData 中的单词信息,基本上就够用了。也许还会包含 MDict 论坛中提供的丰富的词典数据。
方法
首先,通过浏览器的“查看元素”(也有叫“审查元素”)功能,找到获取数据的API。
- 打开 Google 翻译,在网页中任意位置右键>查看元素,然后切换到“网络”选项卡;
- 如果有请求记录,可以点一下“清除”,不点也无所谓;
- 然后查一个单词(比如 about),注意这时请求记录的变化,为了方便查看,点下面的“XHR”过滤一下;
- 如果你刚刚只查了一个单词,这时应该只有两条记录,一个是 GET 方法的请求,一个是 POST 方法的请求。
● 点选 GET 方法的请求,在右边的消息头中可以看到请求的地址(http://translate.google.cn/translate_a/single?client=t&sl=en&tl=zh-CN&hl=zh-CN&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&dt=at&ie=UTF-8&oe=UTF-8&otf=1&ssel=0&tsel=0&kc=6&tk=521885|232363&q=about),这就是我们要想找的 API 之一了,只要把其中的”about“换成你想查到的其它单词就OK了。在右边的响应中可以看到服务器返回的数据,可见返回的是单词的基本信息,还有对应的英文例句。
● 点选 POST 方法的请求,在右边的消息头中同样得到请求地址(http://translate.google.cn/translate_a/t?client=mt&sl=en&tl=zh-CN&hl=zh-CN&v=1.0&format=html&tk=521885|232363),但 POST 的方法的请求与 GET 方法有所不同,POST 方法可以发送数据不是通过 URL 地传递,而是另附一个包。为了看清其形式,可以点击右边的”编辑和重发“,哈哈,一目了然,在最下面的”请求主体“中发现,例句中的空格都被替换成了”%20“,我们也照做就OK了。
定义查询单词基本信息的函数
googleWord[word_] := URLFetch["http://translate.google.cn/translate_a/single?client=t&sl=\ en&tl=zh-CN&hl=zh-CN&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&\ dt=t&dt=at&ie=UTF-8&oe=UTF-8&otf=1&srcrom=1&ssel=0&tsel=0&kc=1&tk=\ 520310|498193&q=" <> word] googleWord["about"]
这个太简单了,以至于都没有必要使用 URLFetch 函数,用 Import 都可以搞定。
不要高兴的太早,此 API 返回的数据格式真令人头疼。很多 API 都支持返回 XML 格式的数据,Google 翻译提供的 API 貌似也是支持的,而且 Google 也提供了查询的 API ,只是它的说明在墙那头,下周回公司了查一查再补吧(也许我会忘记)。还是回来,顺着原思路一头走下去吧,Mma 处理字符串的功能非常强大,只要数据有规律就好。
其实刚开始的时候,处理里它还挺愁的,以为只要把中括号换成花括号就好了。在新科学论坛上问了此问题,在“苹果”的帮助下,才注意到返回的数据中有时候会包含转义字符,这会大大降低 ToExpression 函数的效率。
还是直接说结果,在返回的字符串中,转义字符有两类,一类是 HTML 语言的转义字符,比如 “<”,“>”等;另一类是汉字或特殊字符的转义,统一使用 UTF8 编码,比如用”\u95f2\u4e91\u8c37“表是汉字”闲云谷“。下面给出重新定义的函数:
googleWord[word_] := Module[{rs = URLFetch[ "http://translate.google.cn/translate_a/single?client=t&sl=en&tl=\ zh-CN&hl=zh-CN&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t&\ dt=at&ie=UTF-8&oe=UTF-8&otf=1&srcrom=1&ssel=0&tsel=0&kc=1&tk=520310|\ 498193&q=" <> StringReplace[word, " " -> "%20"], {"Content", "StatusCode"}]}, If[rs[[2]] == 200, Return[ToExpression[ StringReplace[ StringReplace[ rs[[1]], {"[" -> "{", "]" -> "}", ",," -> ",0,", "\\u" ~~ x : RegularExpression["\\s*\\w{4,4}"] :> FromCharacterCode[ FromDigits[StringReplace[x, RegularExpression["\\s"] -> ""], 16], "UTF8"]}], {",," -> ",0,", "{{," -> "{{0,", ",{," -> ",{0,", WordBoundary ~~ "{," -> "{0,", ",}," -> ",0},", ",}}" -> ",0}}", ",}" ~~ WordBoundary -> ",0}", "&" ~~ xs : Shortest[__] ~~ ";" /; StringLength[xs] < 10 :> ImportString["&" ~~ xs ~~ ";", "HTML"]}]]], googleWord[word]]] googleWord["about"]
除了上面提到的原因,这里定义的函数 googleWord 还多了一个功能:当频繁查询时,由于网络原因难免会查询失败,当遇到此情况时,函数 googleWord 会重新发送请求,直到查询成功为止。
定义查询例句翻译的函数
还是先给一个单简的例子,方便阅读主体内容:
googleSentence[Sentence_] := URLFetch["http://translate.google.cn/translate_a/t?client=mt&sl=e=zh-\ CN&hl=zh-CN&ie=UTF-8&oe=UTF-8&v=1.0&format=html&tk=520310|498193", "Method" -> "POST", "BodyData" -> "&q=" <> StringRiffle[StringReplace[Sentence, " " -> "%20"], "&q="]] googleSentence["Good health is above wealth."]
完整的函数定义:
googleSentence[strs_] := Module[{rs = URLFetch[ "http://translate.google.cn/translate_a/t?client=mt&sl=e=zh-CN&\ hl=zh-CN&ie=UTF-8&oe=UTF-8&v=1.0&format=html&tk=520310|498193", \ {"Content", "StatusCode"}, "Method" -> "POST", "BodyData" -> "&q=" <> StringRiffle[StringReplace[#, " " -> "%20"] & /@ (strs), "&q="]]}, If[rs[[2]] == 200, Return[Transpose[{strs, ToExpression[ StringReplace[ rs[[1]], {"[" -> "{", "]" -> "}", "\\u" ~~ x : RegularExpression["\\s*\\w{4,4}"] :> FromCharacterCode[ FromDigits[ StringReplace[x, RegularExpression["\\s"] -> ""], 16], "UTF8"], "&" ~~ xs : Shortest[__] ~~ ";" /; StringLength[xs] < 10 :> ImportString["&" ~~ xs ~~ ";", "HTML"]}]][[;; Length[strs]]]}]], googleSentence[strs]]] googleSentence[{"Good health is above wealth.", "Wasting time is robbing oneself."}]
由于单词的查询是互不相干的,运行这个函数时,Mma 绝大多数时间都是在等待服务器传回数据,所以使用并行命令可以成倍提高速度。在良好的网络中,用4核电脑,速度大概是原来的3.5倍以上。
推荐函数:ParallelMap
另外,如果在你的系统上运行结果有乱码,可以使用下面的函数转换。
FromCharacterCode[ToCharacterCode["这里是你的乱码"], "UTF-8"]
2、通过身份证查询出生地
在百度 API 商店中搜一下”身份证“,就会得到好多查询身份证信息的 API ,选一个免费的 API, 看一下例子,了解一下格式,然后可以用 Mma 进行批量查询了:
find[id_] := URLFetch["http://api.46644.com/idcard?appkey=\ 1307ee261de8bbcf83830de89caae73f&idcard=" <> ToString[id]] find[130322]
这里选用此 API 是由于只输入身份证前6位它就可以给出出生地信息。不过,此 API 不能返回 XML 格式的数据,只支持 JSON 格式,还需要简单处理一下。
关于相应的 API 也可以去其它网站去找,同样是用”查看元素“ 功能即可。其它(手机归属地查询,IP所在地查询,GIS信息查询,查询书籍信息……)API 自己去发现吧,好玩的很!
3、批量下载套图
我主要使用火狐浏览器和 Chrome 浏览器,推荐个火狐浏览器的扩展插件——Image Picker,这个插件可以非常方便下面页面中的图片,可是它并不完美,比如当论坛为图片增加了绽放功能时,Image Picker 无法保存原图(也许是由于我玩的不精)。这时又该 Mma 上场了,怎么那么兴奋呢,原因有三:
- URLFetch 函数支持 Cookies,也支持使用账号和密码,这分明就是个浏览器嘛(看看这个用 Mma 作的网站);
- 很多浏览器的 Cookies 文件都是 SQLite 数据库文件,而 Mma 还支持 SQLite(前面是官网的链接,教程点这儿)!
- 通过此案例,结识了 SQLite 数据库——超轻量级数据库啊!Windows 版只有几百KB,而且不用安装不用配置,只要了解命令行和基本 SQL 语句即可上手。
有前两点,就可以开心快乐地玩耍了
下面以 WIndows10 系统中火狐浏览器的 Cookies 为例。Chrome 浏览器默认参数设置点这里(特别要注意读取时间时需要变换,而且Value字段是加密的)
读取 Cookies 中信息并从网络抓取图片
首先,在火狐浏览器中登录新科学论坛,这样本地就存有此论坛该用户的登录状态的 Cookies 信息了,然后再执行下面的命令:
Module[{},
Label[bigen];
outputdir = "D:/";(*输出目录*)
url = InputString[
Column[{Style["请选择图片格式(至少选择一种):", Bold, 18],
Row[{" ",
Grid[{{Checkbox[Dynamic[jpg]],
" jpg"}, {Checkbox[Dynamic[gif]],
" gif"}, {Checkbox[Dynamic[png]],
" png"}, {Checkbox[Dynamic[bmp]], " bmp"}},
Alignment -> Left]}], Style["\n请输入需要抓取图片的网址:", Bold, 18]}],
"http://"];
url = StringReplace[url, " " -> ""];
imgtype = {".jpg", ".gif", ".png",
".bmp"}[[Flatten[Position[{jpg, gif, png, bmp}, True]]]];
If[url =!= $Canceled &&
Length[imgtype] == 0 && ! StringContainsQ[url, "."] && !
URLExistsQ[url], Goto[bigen];, If[url === $Canceled, Goto[end]];];
domain[url_] :=
StringReplace[URLParse[url]["Domain"],
RegularExpression["^.*\\."] ~~
x : RegularExpression["[^\\.]+\\.[^\\./$]+"] :> x];(*二级域名*)
cookiesName =
"cookies.sqlite";(*火狐浏览器的Cookies文件名,Google浏览器是Cookies*)
(*==================对于同一网站这堆等号之间的部分不需要每次都执行==================*)
cookiesDirectory =
DirectoryName[
First[FileNames[
cookiesName, {"C:/Users/" <> $UserName <>
"/AppData/*/Mozilla/*"}, Infinity]]];(*Cookies所在目录*)
Needs["DatabaseLink`"];
conn = OpenSQLConnection[JDBC["SQLite", cookiesName],
"Location" -> cookiesDirectory,
"RelativePath" -> True];(*以SQLite数据库方式连接Cookies文件*)
sql = "Select
host
,path
,case isSecure when 0 then 'False' else 'True' end secure
,datetime(expiry,'unixepoch','localtime') expiry
,name
,value
From moz_cookies
Where host like '%" <> domain[url] <>
"';";(*针对火狐的SQL,Google浏览器的SQL有所不同,请参见:http://forensicswiki.org/\
wiki/Google_Chrome#Cookies*)
rs = SQLExecute[conn, sql];(*结果集*)
CloseSQLConnection[conn];(*关闭数据库*)
myCookies = {"Domain" -> #[[1]], "Path" -> #[[2]],
"Secure" -> #[[3]], "Expires" -> #[[4]], "Name" -> #[[5]],
"Value" -> #[[6]]} & /@ rs;(*转换成URLFetch需要的Cookes格式*)
(*==================对于同一网站这堆等号之间的部分不需要每次都执行==================*)
html = URLFetch[url,
"Cookies" -> myCookies];(*核心命令:返回HTML文本,图片地址就在这里了*)
charset =
If[MemberQ[{"gbk", "GBK"},
First[Flatten[
StringCases[html,
"<meta" ~~ x : Shortest[__] ~~ "/>" :>
StringCases[x,
"charset=" ~~ y : RegularExpression["\\w+"] :> y]]]]],
"CP936", "UTF8"];(*确定论坛所用字符集,国内一般就两种:GBK和UTF8*)
imgs = If[StringTake[#, 7] == "http://", #,
"http://" <> URLParse[url]["Domain"] <> "/forum/" <> #] & /@
Flatten[StringCases[#, {"src=\"" ~~ x : Shortest[__] ~~ "\"" :> x,
"zoomfile=\"" ~~ x : Shortest[__] ~~ "\"" :> x}] & /@
Flatten[StringCases[html,
"<img" ~~ Shortest[__] ~~
"/>"]]];(*对于不同的论坛,其中的"/forum/"有时是没有的,但要保留一个斜杠*)
imgs = Select[
imgs, ! StringContainsQ[#, "size=small"] && !
StringContainsQ[#, "size=middle"] && !
StringContainsQ[#, "/common/"] && !
StringContainsQ[#, "/smiley/"] &&
MemberQ[imgtype, StringTake[#, -4]] &];(*直接的图片地址*)
title = FromCharacterCode[
ToCharacterCode[
StringRiffle[
StringReplace[
Reverse[Flatten[
StringSplit[
StringCases[html,
"<title>" ~~ x : Shortest[__] ~~ "<" :> x],
" - "]]][[2 ;;]], " " -> ""], "_"]],
charset];(*贴子标题,用以保存图片时创建目录用*)
dir = outputdir <> title;
If[! DirectoryQ[dir], CreateDirectory[dir]];
ParallelMap[
Export[dir <> "/" <>
First[StringCases[#,
x : Except["/"] .. ~~ RegularExpression["$"] :> x]],
Import[#]] &, imgs];(*下载图片*)
Label[end];]
由于很多论坛需要登录之后才可以查看图片和附件,所以在 Mma 中也需要登录。而论坛程序一般都是通过本地的 Cookies 信息来验证用户是否登录,如果 Cookies 中已经存有登录信息而且还没有过期,那么就可以省掉通过用户名+密码的方式验证了,这就是此法可行的原因。
另外,程序中已经过滤掉了表情、头像和每页都有的图片。针对不同的网站,还要小小地修改一下。
最后,还可以从缓存中直接取图,那样的速度比此法快太多了,在火狐浏览器中输入“about:cache”可以查看缓存信息。火狐浏览器的缓存文件都存在 “C:\Users\%USERNAME%\AppData\Local\Mozilla\Firefox\Profiles\****.default\cache2\entries”里面了。想想用不上 URLFetch 还是不写在这儿了。
其中 %USERNAME% 是此时你在电脑上登录的用户名,**** 没仔细查过,貌似不同的电脑不一样。在 Mma 可以这样写,以快速打开该文件夹(还是在 Windows 10 中):
SystemOpen[ First[FileNames[ "C:\\Users\\" <> $UserName <> "\\AppData\\Local\\Mozilla\\Firefox\\Profiles\\*.default\\cache2\\\ entries"]]]