在上一篇《用 PowerShell 批量下载某站点的资源文件》中,我们发现使用 PowerShell 的 Invoke-WebRequest 命令存在若干问题。
权版信息
本文永久链接:https://1983cc.github.io/2018/01/28/用puppeteer下载某站点的文件/
在上一篇《用 PowerShell 批量下载某站点的资源文件》中,我们发现使用 PowerShell 的 Invoke-WebRequest 命令存在若干问题。
权版信息
本文永久链接:https://1983cc.github.io/2018/01/28/用puppeteer下载某站点的文件/
现在有某站点,它的资源主要是 mp3 音频,站点不提供直接批量下载,只能按目录逐级点击,进入某专辑的文件列表,点某文件条目后进入,点击下载按钮转到下载处理页,成功后会接到响应的资源链接,chrome 会打开新标签页播放。
但某专辑动辄有上百个的文件条目,逐一的点专辑列表下载非常繁琐,所以考虑用工具。用工具自动化批量获取文件的方法,大致可归为爬虫技术,涉及到对 web 网页内容的抓取和解析,最终目的是获得文件地址列表。
抓取 web 网页内容的方法和技术有很多,因平时常用 PowerShell 做 Windows 的自动化操作,所以考虑用 PowerShell,具体来说就是用 PowerShehll 的 Invoke-WebRequest 命令。
参考微软官方文档,定义如下:
Invoke-webRequest
[-UseBasicParsing]
[-Uri]<Uri>
[-WebSession <WebRequestSection>]
[-SessionVariable <String>]
[-Credential <PSCredential>]
[-UseDefaultCredentials]
[-CertificateThumbprint <String>]
[-Certificate <X509Certificate>]
[-UserAgent <String>]
[-DisableKeepAlive]
[-TimeoutSec <Int32>]
[-Headers <IDictionary>]
[-MaximumRedirection <Int32>]
[-Method <WebRequestMethod>]
[-Proxy <Uri>]
[-ProxyCredential <PSCredential>]
[-ProxyUseDefaultCredentials]
[-Body <Object>]
[-ContentType <String>]
[-TransferEncoding <String>]
[-InFile <String>]
[-OutFile <String>]
[-PassThru]
[<CommonParameters>]
具有如下成员:
void Dispose()
bool Equals(System.Object obj)
int GetHashCode()
type GetType()
string ToString()
Microsoft.PowerShell.Commands.WebCmdletElementCollection AllElements { get; }
System.Net.WebResponse BaseResponse {get;set;}
string Content {get;}
Microsoft.PowerShell.Commands.FormObjectCollection Forms {get;}
System.Collections.Generic.Dictionary[string,string] Headers {get;}
Microsoft.PowerShell.Commands.WebCmdletElementCollection Images {get;}
Microsoft.PowerShell.Commands.WebCmdletElementCollection InputFields {get;} Microsoft.PowerShell.Commands.WebCmdletElementCollection Links {get;}
mshtml.IHTMLDocument2 ParsedHtml {get;}
string RawContent {get;}
long RawContentLength {get;}
System.IO.MemoryStream RawContentStream {get;}
Microsoft.PowerShell.Commands.WebCmdletElementCollection Scripts {get;}
int StatusCode {get;}
string StatusDescription {get;}
Invoke-WebRequest 命令向 web 页或 web 服务发送 HTTP,HTTPS,FTP 和 FILE 请求,解析响应并返回表单、链接、图像和其他 HTML 元素,返回类型为 HtmlWebResponseObject ,具体用法示例可参考官网文档,需要指出的是,该命令可用于一般HTTP请求场景,如保存认证信息的 WebSession 参数,对于大多数互联网应用是够用的,但也存在应付不了的场景,详情见下一篇关于 puppeteer 的介绍。
基于以上对于 Invoke-WebRequest 指令的认识,我们可以写批量获取下载地址的代码了。该资源网站的资源路径格式为 “作者名/专辑名/专辑列表/资源文件”,本文关注说明如何抓取资源地址,所以只从”专辑列表”页面开始抓取。
某资源专辑列表页的地址如下:
$downloadListUrl = "http://www.pingshu8.com/MusicList/mmc_7_4906_1.htm"
直接请求获得下载列表:
$downloadListWebPage = Invoke-WebRequest $downloadListUrl
# list4 是从返回的 web 页中找到的列表所在的 div 元素的类名
$downloadList = $downloadListWebPage.ParsedHtml.body.getElementsByClassName("list4")
到现在获得专辑列表的HTML代码段,$downloadList 对象对应的类型不包括任何方便获取列表项的方法,还好范围已缩减至此,正是需要祭出正则表达式的时候了:
# 从 $downloadList 中解析出地址并转换成集合
$urlPattern = "/down_\d*.html"
# 使用Regex 类匹配出地址并生成集合
$addresses = [Regex]::Matches($downloadList[0].innerHTML,$urlPattern,"IgnoreCase")
$addressList = New-Object Collection.Generic.List[string]
$downloadBaseUrl = "http://www.pingshu8.com"
$addresses | % { $addressList.Add($downloadBaseUrl + $_.Value) }
到现在我们就获取了专辑列表第一页的列表内容,要获得全部列表项,需要逐页访问,可以把上述代码整理如下:
$entryListUrl = "http://www.pingshu8.com/MusicList/mmc_7_4906_1.htm"
$downloadBaseUrl = "http://www.pingshu8.com"
$addressList = New-Object Collections.Generic.List[string]
# 递归获取专辑列表全部地址
function GetAddressList($url)
{
$downloadListWebPage = Invoke-WebRequest $url
$downloadList = $downloadListWebPage.ParsedHtml.body.getElementsByClassName("list4")
$urlPattern = "/down_\d*.html"
$addresses = [Regex]::Matches($downloadList[0].innerHTML,$urlPattern,"IgnoreCase")
$addresses | % { $addressList.Add($downloadBaseUrl + $_.Value) }
# 如果有下一页,存在形如 <a href="/Musiclist/mmc_7_4906_4.htm">末页</a> 的字符,作为递归条件
# "末页"二字在 PS 中可能显示为乱码,造成不匹配,需要转码
$nextPageUrlSectionPattern = @"
<a href=\"\/Musiclist\/[\b,\w]{1,}\.htm\"\>末页</a>
"@
$nextPageSection = $downloadListWebPage.ParsedHtml.body.getElementsByClassName("list5")
$nextPageUrlSection = [Regex]::Matches($nextPageSection[0],$nextPageUrlSectionPattern,"IgnoreCase")
$nextPageUrlPattern = "\/Musiclist\/[\b,\w]{1,}\.htm\"
$nextPageUrl = [Regex]::Matchers($nextPageSection[0],$nextPageUrlPattern,"IgnoreCase")
if ($nextPageUrlSection -ne $null)
{
GetAddressList($nextPageUrl)
}
return
}
至此我们获得了某专辑全部下载地址,但严格来说还只是下载页面地址,所以任务还没完成,因为网站要求用户点击“下载”按钮,chrome 会自动播放返回的真正的MP3文件,这.就引发了下面两个问题:
执行 js 获得链接解析地址。在上面获取的地址还需要解析下载按钮的动作来获得, js 动作代码如下:
function downfile() {
var downurl = "pingshu://cc%252Fbzmtv%255FInc%252Fdownload%252Easp%253Ffid%253D236632akb%253D%253D";
downurl = decodeURIComponent(decodeURIComponent(downurl.substr(10,downurl.length-1)));
downurl = downurl.substr(2,downurl.length-7);
}
解析出的地址 downurl 形如 “/bzmtv_Inc/download.asp?fid=236632”,使用动态页面提供真正的资源地址,这里问题是如何在 PS 中执行 js 代码。
如何下载到资源。上面动态页面直接用 Invoke-WebRequest 访问,返回的响应没有任何有用的内容,怀疑是网站使用了反爬虫策略。
第1个问题可以用第三方类库解决(如 jint),但第2个问题,网站用了哪种反爬虫策略,事实是 PS 不是专门用来做爬虫的,并且没有专门的模块来处理这方面问题,如果一定要用 PS 也不是不可以,会耗费大量的时间精力,但问题是技术本身就是工具,目的是让使用者更方便,所以这么做是得不偿失的,解决问题的方法是另辟蹊径。
解决上述第2个问题的方法放到另一篇去讲,顺便聊一聊爬虫。
权版信息
本文永久链接:https://1983cc.github.io/2017/12/26/用PowerShell批量下载某站点的文件/
偶然的机会知道了微软发布了 Windows 10 的物联网版本 Windows 10 IoT Core, 距今已经有两年了, 它的官方网站 (https://developer.microsoft.com/en-us/windows/iot
)文档和示例清晰完整, 想起买的树莓派3因为懒于学习 Linux 系开发的知识一直在吃灰, 而一脉相承的 .NET 开发体系可以减少学习成本, 就把系统从 Debian 刷成了 Windows 10 IoT Core 。
从安装系统到开发部署第一个示例应用, 按照官方的 Get Started 部分一步步来就可以了, 此时的系统的版本是 14xxx, 如果想部署一个网站, 在一般的主机上只需要装一个 Web 服务器软件就可以了, 而 Windows 10 IoT 只支持 UWP 应用 (或由 Win32 转化的 UWP 应用), 开发语言可以是 C#, C++ 和 Python, 却没有对应的 UWP 服务器软件, 自然地想到不用专门的服务器软件的 Node.js, 而恰好官方示例中有这么一个 Express 的示例, 在 Visual Studio 用到一个叫 NTVS 的 Node.js 开发工具扩展来方便的调试和部署。
而故事(或者事故)就发生在把系统更新到版本15603之后, 这个版本加入了对 Cortana 的支持, 方便开发所谓的智能助理。
通过 NTVS 提供的 Basic Node.js Express 4 Application (Universal Windows) 模板新建一个 Express Web 项目, 把属性的调试机地址改成 Raspberry Pi 的地址, 启动调试, 在输出窗口中会报出错误:
Unhandled exception at line 234, column 9 in Unknown script code (1)
0x800a1624 - JavaScript runtime error: Reflect.apply: argument is not an array or array-like object
显示发生错误的文件是个动态生成的文件, 显然问题出在运行环境上, 现在看来应该看看 NTVS 的源代码
求助文档, 文档中有个类似 Hello World 的 Express 示例, 其中分明写着准备条件有 VS 2017, 于是就下载安装, 之后再装 NTVS, 找遍上下, 除了一个用于 2017 预览版没有对应的版本, 自然 VS 2017 中也不会出现这类模板可使用, 期间还试过在开发机上安装各种版本的 Node.js for Chakra 来尝试排除部署错误 。
由此陷入了困境, 在某刻灵光一闪, 为什么一定要通过工具部署呢?
Node.js 项目既然可以在 PC 上方便的部署, 也可以在安装 Windows 10 IoT Core 的 ARM 的设备上试一试, 于是就下载 GitHub 上 Node.js ChakraCore 的项目给的 Node.js (Chakra) 发布包直接在设备上运行 Node.js 项目, 开始还错了版本, 执行 “node.exe 入口js文件” 时, 发现报出跟上在面一样的错误, 这样可以推断 NTVS 发生这个错误的原因可能是用了旧版的 node.js 执行部署。
用最新版本的 Node.js 启动 Express 网站不会报错, 还以为大功造成了, 没想到存在以下问题
首先是站点启动后, 从外部无法访问, 排除了端口号的问题, 基本上可以确定是请求响应被拦截了, 在 Windows Device Portal 中查看进程, 发现有些进程的权限是 NT_AUTHORITY/SYSTEM
, 而 node 进程是 Administrator, 官方文档中描述 Windows IoT Core 系统不同于 PC 操作系统的开放性, 基于物理网设备的专一性, 在安全方面上采用的是一种防御策略, 即对它不能识别的代码有限制。
于是尝试关闭设备的防火墙, 一试还真的可以访问了, 但是还是会报错, 可能是因为直接复制到设备上的网站文件缺少一些并不是网站文件夹下的文件, 由此看看 NTVS 的源代码也是必要的, 可以知道把 Node.js Express 项目部署到 Windows 10 IoT Core 系统中都做了哪些事, 如果有时间可以看一看, 留待以后再说。
至此放弃, 但收获是通过这些调查研究对问题的原因形成了大概的轮廓。于是就考虑如果要部署这类项目, 还是用更为通用成熟的 Linux 系统, 这个问题的解决还是等 NTVS 更新吧。
Windows 10 IoT Core 的目标用户还是设备制造商, 应用开发的平台主要还是 UWP, 所以 NTVS 的更新也不及时, 对于解决这种问题, 就需要拿出专业的精神,足够的时间, 读 NTVS 源码, 读 node.js for chakra 源码, 了解 Windows 10 IoT Core 的方方面面, 一句话, 这可能不是非专业初学者能玩的, 但在查找问题时却可以提升开发者分析解决问题的能力, 还能学到更多的东西。
权版信息
本文永久链接:https://1983cc.github.io/2017/04/27/Windows-IoT-Core-上部署-Express-项目的一些问题/
修改记录
- 版本:1;修改时间:2017/2/17
平时给孩子拍照片有很多,每个家庭成员手机上的照片因为存储空间不足需要定期清理并汇总到我这里。每次把照片备份到一个文件夹中,文件夹命名格式是 userName-deviceType-backupDate。现在需要从这些文件中选一些照片,想让每个人都能在自己的设备上选择符合的照片。
通过搭建一个Web服务器,让每个人都可以在自己的设备上管理是比较方便的。在GitHub上,LyChee作为一个照片管理系统获得了大量关注,是这次实现个人照片管理的首选。
lychee 是用 php 写的,安装说明中要求 php 版本高于 5.5,目前开发机上安装的是 php 7,Apache 是 2.4,lychee 需要 MySql 数据库,当然还要从 github 上把 lychee 源码拉下来。
把 lychee 从 GitHub 上拉下来后,按照项目中的安装说明,对它的 src 目录先 build 。启动 Apache 服务,并注意要启动 MySql 服务。
配置 Apache 。在这里只简单的在配置文件中加一个 Virtual Host 用于托管站点,如果需要更详细的配置请深入研究 Apache 服务器的配置管理。Virtual Host 中配置了 lychee 的 IP 、端口号和部署的物理地址,如何配置开发人员都知道。
配置 php 。 按照 lychee 的安装说明安装 lychee 需要的 php 组件,其实就是把 php.ini 文件中注释的一些 extension 解注,如果不安装启用会在使用中报各种诡异的错误。
打开浏览器访问 lychee 的地址,它会给出一个安装配置界面,只需要给出 MySql 数据库的用户名和密码就可以了,其它完全可以用默认的,之后系统会要一个 lychee 系统管理员的用户名和密码。搭建完成。
使用中发现存在以下几方面的问题:
从 Server 照片导入存在问题。系统提供了三种导入照片的方式,而在试用从 Server 导入时,如果提供了一个 USB 外接设备的路径无法导入,使用本地磁盘的路径,在导入过程中系统显示一直在 Improting,查看它的上传目录生成了一部分 thumbnail 图,还有一些大图,并且 thumbnail 图和大图的数量也不一致。建议还是用 upload 方式批量选择文件夹中的图像进行上传。
从 Star 和 Recent 等固定默认目录中 download 报错。从 GitHub 上的 Issues 中也没有找到答案,还好自建的相册目录没有下载问题。报错信息如下图
从苹果设备访问站点存在问题,安卓设备没有问题。但用开发机做服务器存在访问速度慢的问题,而 CPU 和内存的使用率并不高,还需要研究是不是家庭局域网的原因。
权版信息
本文永久链接:https://1983cc.github.io/2017/02/13/PersonalImageServerWithLychee/
在淘宝上购得 树莓派 Raspberry Pi 3 Modal B 1个,HDMI 转 VGA线 1个,外壳,散热片,以及预装好 Debian 的 64G CF卡,另在京东购直连网线;电源直接用 Android 手机电源就行,试用没发现电压不稳的情况。
树莓派 的接口很明显,HDMI转接线连好显示器,插入 CF卡, 可以在最后连电源,系统会自动启动
首先要做好网络连接。用直连线把 PC 和 树莓派 连接起来;把键盘接入树莓派,输入 wifi 密码连好无线网;在 PC 上把无线网络的共享设置为 以太网;查找 树莓派 的IP地址,可以在 debian 上用 ifconfig 命令,或者在 PC 上用 arp -a 命令查看(我实际没在这成功看到),这里是 192.168.1.110 。
下载 putty, 输入上面地址用 ssh 连接, 用户名和密码是 pi@raspberry;进入后安装 树莓派 的远程桌面组件,命令是
sudo apt-get install tightvncserver
用 windows 远程桌面 连接, 输入上面地址,用户名和密码和上面相同
这样就可以远程操作 树莓派 了,免去了 接入 鼠标、键盘和显示器
权版信息
本文永久链接:https://1983cc.github.io/2016/12/31/树莓派的安装和使用/