VB/JavaScript并不是Power Query中直接支持的语言,但可以间接运行,是近期才被发掘的隐藏黑科技。
本文中介绍的方法,对于高级用户可根据案例举一反三,通过改写代码实现自己需要的功能。而对于普通用户,可以将代码拿来直接用,不需要理解。
函数Web.Page
用于将Html源码解析为表格结构,比如先写一段简单的Html:
<html> <body> <p>Hello World!</p> </body> </html>
在浏览器中打开后显示出其中的内容:"Hello World!"。
将这段Html作为Web.Page
的参数写入Power Query,解析后并经过多次深化也能找到这段内容。
JavaScript是一种运行在浏览器中的解释型编程语言,在Html中引入JS可以为网页添加各式各样的动态功能。
比如再写一段简单的引入了JS的Html:
<html> <body> <script> document.write('Hello '+'World!'); </script> </body> </html>
在浏览器中打开后显示的内容依然为:"Hello World!",但这个内容并不是事先写死的,而是通过JS脚本将两个字符串'Hello '
和'World!'
动态拼接在一起而得到的。
浏览器能够显示出经过JS运算后的结果,那么在Power Query中又将返回怎样的结果呢?动手测试一下:
同样也返回了经过JS运算后的结果,而不是返回原来的JS代码,这同时也说明了JS是可以被运行在M语言中的。
其中的Html代码也可以简化,去掉其他标签,只留script标签,写作:
= Web.Page("<script>document.write('Hello '+'World!');</script>")
这个就厉害了,这意味着我们只需要修改参数中的JS代码,便能够在Power Query中实现原本编程语言才能实现的功能。
举几个例子:
1、正则表达式
JS中可以使用match等方法使用正则,如'hello123中国456'.match(/\d+/g)
返回["123","456"]。
将这个表达式替换'Hello World!',作为Web.Page
的参数,便能够在M中实现正则,返回"123,456"。
将字符串和正则表达式分别作为变量str和re,封装为自定义函数:
(str as text,re as text)=> Web.Page(Text.Format( "<script> document.write('#[str]'.match(#[re])); </script>", [str=str,re=re])) [Data]{0}[Children]{0}[Children]{1}[Text]{0}
新建一个空查询,复制以上代码粘贴到高级编辑器并将查询命名为RegExp,便能够通过调用自定义函数RegExp
使用正则了。
测试= RegExp("hello123中国456","/\d+/g")
返回"123,456"。
2、导出表格
以往Power Query只能批量导入不能导出,而使用JS便能打破这个限制,前提是必须开启ActiveX控件。
本文中以下大部分案例都需要开启,就不一一重复提醒。开启方法如下:
- 打开IE浏览器,打开Internet选项,或直接按快捷键Alt+X+O。
- 依次点击安全-Internet-自定义级别。
- 找到以"对未标记为可安全执行脚本的ActiveX控件"开头的选项,选择启用。
没错,开启后将使你的电脑变得不安全,所以可在当有需要的时候开启,不需要的时候再关闭。
同样封装为自定义函数,代码为:
(tb as table,path as text)=> let Json = Text.FromBinary(Json.FromValue(Table.ToRows(Table.DemoteHeaders(tb)))), Export = Web.Page( "<script> var fso=new ActiveXObject('Scripting.FileSystemObject'); var f1=fso.CreateTextFile('"&Text.Replace(path,"","/")&"',true); var arr="&Json&"; f1.WriteLine(arr.join('\n')); f1.WriteBlankLines(1); f1.Close(); </script>") in Export
新建一个空查询,复制以上代码粘贴到高级编辑器并将查询命名为Export。
自定义函数需要两个参数,第一参数为需要导出的表,第二参数指定导出的路径,包含文件名和扩展名,但使用的CreateTextFile方法只支持csv和txt。
测试= Export(表,"C:\Users\rages\Desktop\test.csv")
,在桌面上生成了一个test.csv文件。
可使用List.Transform
批量导出多个表,但以防同时导出时产生冲突,最好再使用Function.InvokeAfter
设置延时。
由于该方法是将表先转换为Json再传递到JS,所以导出的数据量有限制。
Ivan Bondarenko对代码进行了测试,当导出20万行15列的表格时没有问题,但当行数增加到三倍时出现了错误。
所以当需要导出的表的量级很大时,可以将一张表拆分为多张表循环导出,Ivan的博客中对此有更深入的介绍。
3、打开并重新保存表格,解决导入错误问题
经常会遇到一些玄学xls,Power Query读取的时候报错,提示"外部表不是预期的格式"。还有一些虽然不报错,但表格内明明有内容读出来却是空表,最典型的就是电商行业生意参谋导出的文件。
遇到这种问题,往往打开文件什么都不做,直接保存后再导入就没问题了。但是每天重复去打开那么多文件还是很麻烦的,有了JS就可以解决这个烦恼。
(path)=>Web.Page( "<script> var oXL = new ActiveXObject('Excel.application'); oWB = oXL.Workbooks.open('"&Text.Replace(path,"","/")&"'); oWB.Save(); oXL.Quit(); </script>")
新建一个空查询,复制以上代码粘贴到高级编辑器并将查询命名为Open_Save,参数中填写文件路径,在导入文件的上一个步骤中调用自定义函数。
测试= Open_Save("C:\Users\rages\Desktop\【生意参谋平台】PC店铺流量来源-2016-11-19_2016-11-19.xls")
未完。。。
一个函数就把别人一门语言给圈进来了。嘿嘿。
也就是说M语言中可以引入正则表达式,对吗
我只是看过曾贤志老师的在VBA中调用正则表达式,不知道能不能也运用到这里来
曾贤志老师第三季的课最后几节就有,主要是用于文字筛选。结合PQ 本身的功能已经能够满足绝大部分使用场景。
好用。
喜欢阳神的文章。真的在讲技术的博客。
调用正则不用打开Active控件吧
对,不需要,下面两个需要
VB部分什么时候继续展开呢,期待好久了。
请教大佬,为什么我这段代码在powerquery中执行没有反应呢?
let
url = "http://basic.10jqka.com.cn/api/stock/export.php?export=main&type=report&code=688036",
visitWebsite = Web.Page("
var a = document.createElement('a');
var blob = new Blob([a.Body]);
var url = window.URL.createObjectURL(blob);
var filename = '123.xls';
a.href = '"&url&"';
a.download = filename;
a.click();
a.remove();
")
in
visitWebsite
请问,第三条中用Open_Save打开指定路径的xls文件之后要怎么操作,我试了还是不能正常获取到需要的表
我也是这样,报错Expression.SyntaxError:应为令牌Identifier。
你好,实际使用中(爬取多页数据)提示"Web.page函数未在100秒超时内完成",应如何解决?
大佬~学习了~不想打赏都不行呀哈哈~
这个对于数据难清洗的,数据量不大的话,好使!
老师,第三步可以详细讲解下如何调用自定义函数不,“在导入文件的上一个步骤中调用自定义函数” 看不懂这一步
这边文章什么时候更新啊。用PQ爬网站数据时候,不是会遇到一些需要登陆的网站吗。是不是可以用嵌套JS的代码自动登陆并获取Cookie啊?该怎么写?
我发现,想封装一个正则表达式的函数并不简单,还要考虑js和pq的转义字符,还有连续空格的转换。
还有1个不解的地方,我有1个53w行的表,用json解析时,有些列要拆分,但有些不用。最后我发现,拆分成每1w行调用1次json解析是最快的,目前也不知道是为什么。