在 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[__] ~~ "/>"]]];(*<span style="color: #ff0000;">对于不同的论坛,其中的"/forum/"有时是没有的,但要保留一个斜杠</span>*) 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"]]]
mma能用来做课堂点名系统吗。。。因为JAVAScript早忘了
貌似Mma在这方面不是长项(我对Mma在手机上的部署不太了解,所以用了“貌似”),还是用Java方便吧。
幸好是选了网络实验才要做,我没选!不然就煎熬啊。
为什么mma绘制的图复制到word中里面文字会“褪色”?
用Export导出,参见http://shuli.xianyungu.com/photo-auto-layout-i-just-want-to-say-the-resolution-setting-in-the-mma-1070
怎么设置某特定的两个单元之间的同一名字的变量不互相影响啊?
做Manipulate的时候经常遇到这种蛋疼事,有时甚至在不改变变量时 观察到变量的值的“涨落” 是不是很诡异啊 可惜没法截图啊