• 信息被删除或无权限查看
  • 通过网页抓取数据 at 2018-08-28 12:37:52

    首先在高级编辑器中,最上面添加一行:

    (x)=>

    然后将代码中第一行的 url 修改为变量 x ,写作:

    源 = Web.BrowserContents(x),

    其他都不动,修改完后,查询 dianping 变成了函数的图标,表示这是一个自定义函数,就可以在查询 web 中调用这个自定义函数。

    假设在查询 web 中有一列字段名为 url ,那么只需要点击添加自定义列,输入:

    = dianping([url])

    即可批量抓取该列中每一个 url 的内容。

  • 请问为什么 M 函数调用百度文字识别 API 提示 image 参数出错? at 2018-08-27 17:37:40

    你的 access_token 是不是用的文档示例上的?
    根据文档的介绍,是需要根据 API KeySecret Key 获取的,有效期为 30 天。
    所以需要请求 2 次,先根据 API KeySecret Key 请求获取到 access_token,然后再根据 access_token 和图片获取文字。代码为:

    let
        token_url = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=xxxxxxxxxxxxxxxxx&client_secret=xxxxxxxxxxxxxx",
        token_headers = [#"Content-Type"="application/json; charset=UTF-8"],
        token = Json.Document(Web.Contents(token_url,[Headers=token_headers,Content=Text.ToBinary("")]))[access_token],
        img_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token="&token,
        img_headers = [#"Content-Type"="application/x-www-form-urlencoded"],
        img_content = "image="&Uri.EscapeDataString(Binary.ToText(File.Contents("C:\xxxxxxxxxxx.png"),BinaryEncoding.Base64)),
        result = Table.FromRecords(Json.Document(Web.Contents(img_url,[Headers=img_headers,Content=Text.ToBinary(img_content)]))[words_result])
    in
        result

    注意修改图片的路径和 API KeySecret Key 为你自己的。

  • 一对多合并查询时如何去重的问题 at 2018-08-27 15:20:51

    因为只需要保留第一次出现的数据,而源数据中并没有反映出现次数的列,所以首先得添加次数列,可参考 《模拟绝对引用累计计数》

    合并查询时,将次数列添加到合并条件中,因为表 1 中的值都是第一次出现,这样表 2 中只有第一次出现的行才会匹配到表 1 中的数据,后面出现的就不会匹配到了 。

    共 3 个查询:

    // 表1
    let
        源 = Excel.CurrentWorkbook(){[Name="表1"]}[Content],
        分组 = Table.Group(源, {"A"}, {"a", each Table.AddIndexColumn(_,"times")}),
        展开 = Table.ExpandTableColumn(分组, "a", {"B", "times"})
    in
        展开
    
    // 表2
    let
        源 = Excel.CurrentWorkbook(){[Name="表2"]}[Content],
        索引 = Table.AddIndexColumn(源, "索引"),
        分组 = Table.Group(索引, {"C"}, {"a", each Table.AddIndexColumn(_,"times")}),
        展开 = Table.ExpandTableColumn(分组, "a", {"D", "索引", "times"})
    in
        展开
    
    // 结果
    let
        源 = Table.NestedJoin(表1,{"A", "times"},表2,{"C", "times"},"表1",JoinKind.RightOuter),
        展开 = Table.ExpandTableColumn(源, "表1", {"C", "D", "索引"}, {"C", "D", "索引"}),
        排序 = Table.Sort(展开,{"索引"})
    in
        排序
  • m 函数小问题 at 2018-08-15 23:27:26

    表中的出勤时间为文本,肯定不能直接相加,那就要先转成可以相加的数据类型。
    哪些可以相加?number 可以,但是很麻烦,那就转成专门表示时间的 duration,转换的函数是 Duration.From
    但是直接转换会报错,因为不符合参数的要求,要求的格式是用冒号间隔,如 10:17,那就得先把文本中的“小时”替换为冒号,去掉最后的“分”。

    = List.Sum(
        List.Transform(
            Source[实际出勤时间],
            each Duration.From(
                Text.Trim(Text.Replace(_,"小时",":"),"分")
                )
        ))

    最后得到 2.01:09:00,即2天1小时09分。

    file

  • 网页抓取-翻页页面地址不变 at 2018-08-15 22:58:21

    1、URL 中的每个参数都有自己的作用,只是可能对于获取你需要的数据而言,某些参数填与不填对返回结果没有影响,所以可以省略。
    参数是由程序员人为命名及定义的,在不同的网页中请求参数都各不相同,那么我是如何知道各个参数的作用以及是否可以省略的呢?只有通过手动测试。
    因为是 GET 方法,请求参数全部附加在 URL 末尾,所以可以直接把 URL 输入到浏览器里打开看下返回的结果。通过不断的删减修改各参数,观察返回结果的变化,最终只保留了其中的几个必要参数,当然你也完全可以不做任何修改将所有参数填上去。
    2、不难发现原 URL 中表示日期的参数为 filter=(tdate=%272018-06-27T00:00:00%27),那么如果要修改日期,也必须要按照原来的格式。代码为:

    let
        url  =  "http://dcfm.eastmoney.com/em_mutisvcexpandinterface/api/js/get?type=RZRQ_DETAIL_NJ&token=70f12f2f4f091e459a279469fe49eca5",
        date =  List.Dates(#date(2018,06,25),5,#duration(1,0,0,0)),
        web  =  Table.Combine(
                    List.Transform(date,each Table.FromRecords(
                        Json.Document(Web.Contents(
                            url&"&filter=(tdate=%27"&Date.ToText(_,"yyyy-MM-dd")&"T00:00:00%27)"
                        ))
                    ))
                )
    in
        web

    其中 Json.Document 参数要求为 any,也就是说可以是由 Web.Contents 返回的 binary,也可以是由 Text.FromBinary 返回的 text,加或不加都可以。对于新手而言,建议是加上,因为如果出错了可以通过返回的 text 内容进行调试排错。

  • 网页抓取-翻页页面地址不变 at 2018-08-15 22:55:40

    有问题的地方挺多的,第二步如果写成 record 的形式,应该是一个字段对应一个值,值是文本要加引号,各个字段中间逗号隔开。最后的 in 之后应为最后一步的步骤名。
    这个网页的请求方式是 GET ,所以还是比较简单的,直接把请求参数附加在 URL 之后即可,在 F12 中能够找到。

    file

    其中大部分参数可省略,如抓取第 4 页,代码为:

    let
        url = "http://dcfm.eastmoney.com/em_mutisvcexpandinterface/api/js/get?type=RZRQ_DETAIL_NJ&token=70f12f2f4f091e459a279469fe49eca5&filter=(tdate=%272018-06-27T00:00:00%27)&p=4&ps=50",
        data = Json.Document(Web.Contents(url)),
        result = Table.FromRecords(data)
    in
        result

    如果要抓所有页码,可以把 URL 中表示页码的参数 p=4 做成变量,再用 List.Transform 批量抓取。
    但实际上在这个网页中,把 URL 末尾最后两个分别表示 page 和 pagesize 的参数去掉,返回的就是全部页码中的数据。

  • 单个 table 导入到 Power Query 展开后为什么会重复一个 table? at 2018-08-15 22:52:01

    注意第一张图表中的 Name 列,命名中有个 FilterDateBase,这是表因被筛选过而产生的隐藏区域,Hidden 列中的 true 和 false 表示该表是否被隐藏。复制到一个新表后,新表没有被筛选,自然不会产生这个隐藏区域。
    PQ 除了可以识别出表中的 sheet,还能识别这个隐藏区域,但实际两个表中的数据是一样的,如果不事先处理就会导致表中的每一行都被重复一次。所以正确的做法是在第一张图的步骤中筛选 Hidden 列,把值为 true 的行筛选掉。

  • PQ 在分隔符后加提取字段用什么函数 at 2018-08-15 22:48:17

    从举的例子来看,还有很多未知的不确定因素,如长度是否都统一?前缀是否都是 G2004
    如果长度统一,那么可以用 Text.At 获取第 7 个字符,然后用 Text.Insert 插入字符,写作:

    = Text.Insert("G2004F30138-0187",12,Text.At("G2004F30138-0187",6))

    但实际情况可能没这么简单,有可能位数不一样,或者最后一段只有两三个字符。
    前缀先不管,将问题简化为:如何将 30138-87 变成 30138-30187
    在 Excel 中的思路很简单,先用 len 分别截取左右的长度,然后用左边的长度减去右边的长度得到 3,再用 left 获取左边的前 3个字符,插入到右边的最前面。
    在 Power Query 中也是一样的思路,只是换了个函数名:

    let
        a = "30138-87",
        b = Text.Split(a,"-"),
        c = Text.Start(a,Text.Length(b{0})-Text.Length(b{1})),
        d = b{0}&"-"&c&b{1}
    in
        d

    《发票号展开》 中有类似的案例。

  • 关于如何使用 Power BI 网抓后加时间戳并保存数据 at 2018-08-15 22:44:13

    不直接支持,但可以借助 JavaScript 来间接实现。
    使用前需要开启 ActiveX 控件,可参考 《在Power Query中使用VB/JavaScript》
    对文中的案例 2 修改自定义函数,以当前的时间戳作为生成的 csv 文件名。

    (tb as table,path as text)=>
    let
        Timestamp = Text.From(Number.IntegerDivide(Duration.TotalSeconds(DateTime.LocalNow()-#datetime(1970,1,1,8,0,0)),1)),
        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&Timestamp&".csv","\","/")&"',true);
                                var arr="&Json&";
                                f1.WriteLine(arr.join('\n'));
                                f1.WriteBlankLines(1);
                                f1.Close(); 
                        </script>")
    in
        Export

    自定义函数需要两个参数,第一参数为表,可以是抓取的结果。第二参数为导出文件的目录,如 C:\
    这样每手动刷新一次,在设置的目录下就会生成一个文件,如 1530011447.csv