精华内容
下载资源
问答
  • 在上一篇《我是怎样让网站HTML5 Manifest》介绍了怎么Manifest做一个离线网页应用,结果被广大网友吐槽说这个东西已经被deprecated,移出web标准了,现在被Service Worker替代了,不管怎么样,Manifest的一些...

    在上一篇《我是怎样让网站用上HTML5 Manifest》介绍了怎么用Manifest做一个离线网页应用,结果被广大网友吐槽说这个东西已经被deprecated,移出web标准了,现在被Service Worker替代了,不管怎么样,Manifest的一些思想还是可以借用的。笔者又将网站升级到了Service Worker,如果是用Chrome等浏览器就用Service Worker做离线缓存,如果是Safari浏览器就还是用Manifest,读者可以打开这个网站https://www.rrfed.com感受一下,断网也是能正常打开。

    1. 什么是Service Worker

    Service Worker是谷歌发起的实现PWA(Progressive Web App)的一个关键角色,PWA是为了解决传统Web APP的缺点:

    (1)没有桌面入口

    (2)无法离线使用

    (3)没有Push推送

    那Service Worker的具体表现是怎么样的呢?如下图所示:

    ee8afe4d8d622604e2015d05726f812a.png

    Service Worker是在后台启动的一条服务Worker线程,上图我开了两个标签页,所以显示了两个Client,但是不管开多少个页面都只有一个Worker在负责管理。这个Worker的工作是把一些资源缓存起来,然后拦截页面的请求,先看下缓存库里有没有,如果有的话就从缓存里取,响应200,反之没有的话就走正常的请求。具体来说,Service Worker结合Web App Manifest能完成以下工作(这也是PWA的检测标准):

    11b0701d7576a8c3f58e832201f4de40.png

    包括能够离线使用、断网时返回200、能提示用户把网站添加一个图标到桌面上等。

    2. Service Worker的支持情况

    Service Worker目前只有Chrome/Firfox/Opera支持:

    7de0db77526cbd3f1d473abfebd99f9f.png

    Safari和Edge也在准备支持Service Worker,由于Service Worker是谷歌主导的一项标准,对于生态比较封闭的Safari来说也是迫于形势开始准备支持了,在Safari TP版本,可以看到:

    824d7000db37256732b181b1743f39ce.png

    在实验功能(Experimental Features)里已经有Service Worker的菜单项了,只是即使打开也是不能用,会提示你还没有实现:

    63064ef503d9e6e1716c9a9932ed919a.png

    但不管如何,至少说明Safari已经准备支持Service Worker了。另外还可以看到在今年2017年9月发布的Safari 11.0.1版本已经支持WebRTC了,所以Safari还是一个上进的孩子。

    Edge也准备支持,所以Service Worker的前景十分光明。

    3. 使用Service Worker

    Service Worker的使用套路是先注册一个Worker,然后后台就会启动一条线程,可以在这条线程启动的时候去加载一些资源缓存起来,然后监听fetch事件,在这个事件里拦截页面的请求,先看下缓存里有没有,如果有直接返回,否则正常加载。或者是一开始不缓存,每个资源请求后再拷贝一份缓存起来,然后下一次请求的时候缓存里就有了。

    (1)注册一个Service Worker

    Service Worker对象是在window.navigator里面,如下代码:

    window

    在页面load完之后注册,注册的时候传一个js文件给它,这个js文件就是Service Worker的运行环境,如果不能成功注册的话就会抛异常,如Safari TP虽然有这个对象,但是会抛异常无法使用,就可以在catch里面处理。这里有个问题是为什么需要在load事件启动呢?因为你要额外启动一个线程,启动之后你可能还会让它去加载资源,这些都是需要占用CPU和带宽的,我们应该保证页面能正常加载完,然后再启动我们的后台线程,不能与正常的页面加载产生竞争,这个在低端移动设备意义比较大。

    还有一点需要注意的是Service Worker和Cookie一样是有Path路径的概念的,如果你设定一个cookie假设叫time的path=/page/A,在/page/B这个页面是不能够获取到这个cookie的,如果设置cookie的path为根目录/,则所有页面都能获取到。类似地,如果注册的时候使用的js路径为/page/sw.js,那么这个Service Worker只能管理/page路径下的页面和资源,而不能够处理/api路径下的,所以一般把Service Worker注册到顶级目录,如上面代码的"/sw-3.js",这样这个Service Worker就能接管页面的所有资源了。

    (2)Service Worker安装和激活

    注册完之后,Service Worker就会进行安装,这个时候会触发install事件,在install事件里面可以缓存一些资源,如下sw-3.js:

    const 

    通过上面的操作,创建和添加了一个缓存库叫fed-cache,如下Chrome控制台所示:

    f8561bc84c46b80d9ad7e2ceb0a02a5a.png

    Service Worker的API基本上都是返回Promise对象避免堵塞,所以要用Promise的写法。上面在安装Service Worker的时候就把首页的请求给缓存起来了。在Service Worker的运行环境里面它有一个caches的全局对象,这个是缓存的入口,还有一个常用的clients的全局对象,一个client对应一个标签页。

    在Service Worker里面可以使用fetch等API,它和DOM是隔离的,没有windows/document对象,无法直接操作DOM,无法直接和页面交互,在Service Worker里面无法得知当前页面打开了、当前页面的url是什么,因为一个Service Worker管理当前打开的几个标签页,可以通过clients知道所有页面的url。还有可以通过postMessage的方式和主页面互相传递消息和数据,进而做些控制。

    install完之后,就会触发Service Worker的active事件:

    this

    Service Worker激活之后就能够监听fetch事件了,我们希望每获取一个资源就把它缓存起来,就不用像上一篇提到的Manifest需要先生成一个列表。

    你可能会问,当我刷新页面的时候不是又重新注册安装和激活了一个Service Worker?虽然又调了一次注册,但并不会重新注册,它发现"sw-3.js"这个已经注册了,就不会再注册了,进而不会触发install和active事件,因为当前Service Worker已经是active状态了。当需要更新Service Worker时,如变成"sw-4.js",或者改变sw-3.js的文本内容,就会重新注册,新的Service Worker会先install然后进入waiting状态,等到重启浏览器时,老的Service Worker就会被替换掉,新的Service Worker进入active状态,如果不想等到重新启动浏览器可以像上面一样在install里面调skipWaiting:

    this

    (3)fetch资源后cache起来

    如下代码,监听fetch事件做些处理:

    this

    先调caches.match看一下缓存里面是否有了,如果有直接返回缓存里的response,否则的话正常请求资源并把它放到cache里面。放在缓存里资源的key值是Request对象,在match的时候,需要请求的url和header都一致才是相同的资源,可以设定第二个参数ignoreVary:

    caches

    表示只要请求url相同就认为是同一个资源。

    上面代码的util.fetchPut是这样实现的:

    let 

    需要注意的是跨域的资源不能缓存,response.status会返回0,如果跨域的资源支持CORS,那么可以把request的mod改成cors。如果请求失败了,如404或者是超时之类的,那么也直接返回response让主页面处理,否则的话说明加载成功,把这个response克隆一个放到cache里面,然后再返回response给主页面线程。注意能放缓存里的资源一般只能是GET,通过POST获取的是不能缓存的,所以要做个判断(当然你也可以手动把request对象的method改成get),还有把一些个人不希望缓存的资源也做个判断。

    这样一旦用户打开过一次页面,Service Worker就安装好了,他刷新页面或者打开第二个页面的时候就能够把请求的资源一一做缓存,包括图片、CSS、JS等,只要缓存里有了不管用户在线或者离线都能够正常访问。这样我们自然会有一个问题,这个缓存空间到底有多大?上一篇我们提到Manifest也算是本地存储,PC端的Chrome是5Mb,其实这个说法在新版本的Chrome已经不准确了,在Chrome 61版本可以看到本地存储的空间和使用情况:

    1ce5f3f0819f127ce905926911835c6e.png

    其中Cache Storage是指Service Worker和Manifest占用的空间大小和,上图可以看到总的空间大小是20GB,几乎是unlimited,所以基本上不用担心缓存会不够用。

    (4)cache html

    上面第(3)步把图片、js、css缓存起来了,但是如果把页面html也缓存了,例如把首页缓存了,就会有一个尴尬的问题——Service Worker是在页面注册的,但是现在获取页面的时候是从缓存取的,每次都是一样的,所以就导致无法更新Service Worker,如变成sw-5.js,但是PWA又要求我们能缓存页面html。那怎么办呢?谷歌的开发者文档它只是提到会存在这个问题,但并没有说明怎么解决这个问题。这个的问题的解决就要求我们要有一个机制能知道html更新了,从而把缓存里的html给替换掉。

    Manifest更新缓存的机制是去看Manifest的文本内容有没有发生变化,如果发生变化了,则会去更新缓存,Service Worker也是根据sw.js的文本内容有没有发生变化,我们可以借鉴这个思想,如果请求的是html并从缓存里取出来后,再发个请求获取一个文件看html更新时间是否发生变化,如果发生变化了则说明发生更改了,进而把缓存给删了。所以可以在服务端通过控制这个文件从而去更新客户端的缓存。如下代码:

    this

    通过响应头header的content-type是否为text/html,如果是的话就去发个请求获取一个文件,根据这个文件的内容决定是否需要删除缓存,这个更新的函数util.updateHtmlPage是这么实现的:

    let 

    代码先去获取一个json文件,一个页面会对应一个json文件,这个json的内容是这样的:

    {

    里面主要有一个updateTime的字段,如果本地内存没有这个页面的updateTime的数据或者是和最新updateTime不一样,则重新去获取 html,然后放到缓存里。接着需要通知页面线程数据发生变化了,你刷新下页面吧。这样就不用等用户刷新页面才能生效了。所以当刷新完页面后用postMessage通知页面:

    let 

    并规定type: 1就表示这是一个更新html的消息,然后在页面监听message事件:

    if 

    然后当我们需要更新html的时候就更新json文件,这样用户就能看到最新的页面了。或者是当用户重新启动浏览器的时候会导致Service Worker的运行内存都被清空了,即存储页面更新时间的变量被清空了,这个时候也会重新请求页面。

    需要注意的是,要把这个json文件的http cache时间设置成0,这样浏览器就不会缓存了,如下nginx的配置:

    location 

    因为这个文件是需要实时获取的,不能被缓存,firefox默认会缓存,Chrome不会,加上http缓存时间为0,firefox也不会缓存了。

    还有一种更新是用户更新的,例如用户发表了评论,需要在页面通知service worker把html缓存删了重新获取,这是一个反过来的消息通知:

    if 

    Service Worker也监听message事件:

    const 

    根据不同的消息类型调不同的回调函数,如果是1的话就是删除cache。用户发表完评论后会触发刷新页面,刷新的时候缓存已经被删了就会重新去请求了。

    这样就解决了实时更新的问题。

    4. Http/Manifest/Service Worker三种cache的关系

    要缓存可以使用三种手段,使用Http Cache设置缓存时间,也可以用Manifest的Application Cache,还可以用Service Worker缓存,如果三者都用上了会怎么样呢?

    会以Service Worker为优先,因为Service Worker把请求拦截了,它最先做处理,如果它缓存库里有的话直接返回,没有的话正常请求,就相当于没有Service Worker了,这个时候就到了Manifest层,Manifest缓存里如果有的话就取这个缓存,如果没有的话就相当于没有Manifest了,于是就会从Http缓存里取了,如果Http缓存里也没有就会发请求去获取,服务端根据Http的etag或者Modified Time可能会返回304 Not Modified,否则正常返回200和数据内容。这就是整一个获取的过程。

    所以如果既用了Manifest又用Service Worker的话应该会导致同一个资源存了两次。但是可以让支持Service Worker的浏览器使用Service Worker,而不支持的使用Manifest.

    5. 使用Web App Manifest添加桌面入口

    注意这里说的是另外一个Manifest,这个Manifest是一个json文件,用来放网站icon名称等信息以便在桌面添加一个图标,以及制造一种打开这个网页就像打开App一样的效果。上面一直说的Manifest是被废除的Application Cache的Manifest。

    这个Maifest.json文件可以这么写:

    {
      

    icon需要准备多种规格,最大需要512px * 512px的,这样Chrome会自动去选取合适的图片。如果把display改成standalone,从生成的图标打开就会像打开一个App一样,没有浏览器地址栏那些东西了。start_url指定打开之后的入口链接。

    然后添加一个link标签指向这个manifest文件:

    <

    这样结合Service Worker缓存:

    bdfe5da112f93a2e3c9272b19117ebd1.png

    把start_url指向的页面用Service Worker缓存起来,这样当用户用Chrome浏览器打开这个网页的时候,Chrome就会在底部弹一个提示,询问用户是否把这个网页添加到桌面,如果点“添加”就会生成一个桌面图标,从这个图标点进去就像打开一个App一样。感受如下:

    9868a926fa21d7dcd614e35dead395f4.png

    比较尴尬的是Manifest目前只有Chrome支持,并且只能在安卓系统上使用,IOS的浏览器无法添加一个桌面图标,因为IOS没有开放这种API,但是自家的Safari却又是可以的。

    综上,本文介绍了怎么用Service Worker结合Manifest做一个PWA离线Web APP,主要是用Service Worker控制缓存,由于是写JS,比较灵活,还可以与页面进行通信,另外通过请求页面的更新时间来判断是否需要更新html缓存。Service Worker的兼容性不是特别好,但是前景比较光明,浏览器都在准备支持。现阶段可以结合offline cache的Manifest做离线应用。

    相关阅读:

    1. 为什么要把网站升级到HTTPS
    2. 怎样把网站升级到http/2
    3. 我是怎样让网站用上HTML5 Manifest
    展开全文
  • 编程本身是跟年龄无关的一件事,不论你现在是十四五岁,还是四五十岁,如果你热爱它,并且愿意持续投入其中,必定会有所收获。...当我在电脑上需要翻译一个单词时,我会这样:打开浏览器 => 打开百度翻译...

    a6eb7facc84793c4ab4c57714fcf20cb.png
    编程本身是跟年龄无关的一件事,不论你现在是十四五岁,还是四五十岁,如果你热爱它,并且愿意持续投入其中,必定会有所收获。
    本文就来自编程教室一位“小”读者的投稿(互助学习1群里的同学应该对作者的名字很熟悉吧)。我看着他不停地产出新的代码和技术文章,不禁感叹“后生可畏”。
    这是一个爬虫基础分析和操作的开发案例,在此分享给大家。

    当我在电脑上需要翻译一个单词时,我会这样做:

    打开浏览器 => 打开百度翻译 => 输入单词 => 得到翻译

    不过有时候,当我在命令行环境下写代码的时候,懒得再切换到浏览器里等待页面的加载。

    于是我就想,能否用万能的 python 制作一个命令行下的翻译工具呢?

    说干就干!

    网上搜索了点信息,加上一顿操作,最终被我捣鼓出来了。来看下最终的效果:

    63f7981b08a1d26c5cfbd1bb0cdf0bd8.png

    在这个工具中,我用了百度有道以及谷歌(可选)的翻译。

    开发思路

    其中获取有道翻译和百度翻译的原理都差不多,这里我们选取百度翻译来玩耍一下啦:

    首先打开百度翻译:http://fanyi.baidu.com

    a2a5370faa5adfd60327dd85589af3e6.png

    接下来右键点击检查,选择 Network 中的 XHR (向服务器发送的数据请求)。然后我们在输入框里输入 save 这个单词,我们可以看到一时间这里面多了几个 sug 标签,点开一看:

    6390cc586c2793aa307de07465f6b4a8.png

    可以看到,这个 sug 请求里面就有我们想要的数据。点进去,Request URL 就是我们需要请求的网址,另外在 Request Method 看到是 POST 方式,也就是提交表单:

    a2576392e4e7560399e141dd3a049b7f.png

    移到最后,我们看见了提交的表单参数:

    88b05a08229d9a3d482f24d51700012f.png

    Form Data 中,我们看见表单是一个字典:{'kw': 'save'},这里 save 是我们自己输入的,也就是我们要查询的单词,因此我们使用 requests.post 来模拟这个请求:

    # 网址和表单
    

    我们可以试着打印出返回的东西,你会发现是以 JSON 格式返回的,因此需要引入内置库 json 来解析:

    content 

    在打印出的 json 格式的文本中我们发现翻译结果在['data'][0]['v'](刚刚的图片里我们也看见了),接下来提取并返回:

    result 

    这样就完成了提交表单了,实现百度翻译了。

    这里用到的就是一些常用的爬虫分析和操作,有道翻译也基本类似,不再赘述,具体操作可参加文末给出的完整代码。

    接下来我们来看看与众不同的谷歌翻译,这个玩意实在有些复杂。谷歌翻译是有 api 的,但是要钱……我不想付钱……网上有大佬给出了解决方案(这里我们简单地看一下原理):

    a84c218b4aa6927d99597529b2a034aa.png

    按照近似百度翻译的方式操作,我们看到这里多出了一个请求,这里面有我们想要的翻译结果:

    1d180e362e4105b238d8c6a14548e5e4.png

    我们回到之前的 Request URL 看一下,发现前面的直接复制就好了,q 是我们需要翻译的词,但是这个 tk 参数却是个棘手的参数。我代码里用到的解法是网上的,仿照谷歌翻译页面上的提供的函数,用 JavaScript 去计算出了这个参数(我怀疑他就是拿的前端源码,没仔细对照)。

    那么如何在 python 中执行 JavaScript 代码呢,我们可以通过 pyexecjs 这个库(注意这个库导入名是 execjs)。这个部分的源码就在 Py4Js 这个类里边了。

    特别说明下,因为无法直接使用谷歌翻译,所以我把它做成了可选项,默认不会调用。

    使用方法

    我已经将这个项目发布到了 pypi,你可以通过 pip 命令按如下方式安装:

    pip install quicktranslate

    用起来也是非常的简单(加上 -g 可增加谷歌翻译结果):

    trans -t 你要翻译的东西

    示例:

    你好
    

    获取本案例完整代码,请在公众号“Crossin的编程教室”中回复关键字:翻译

    作者:pynickle

    ------

    一起学,走得远!

    欢迎搜索:Crossin的编程教室

    展开全文
  • 本文转自:DF创客社区-未经许可不可转载原文链接(附件请于原文下方下载):新款ws2812灯带做一个简单的窗花-创意生活论坛-DF创客社区​mc.dfrobot.com.cn作者:屌丝王小明 很高兴提前拿到了DF即将上架的新品——...

    3d0348c80ae9c54a7431e516ff251a7e.png

    本文转自:DF创客社区-未经许可不可转载

    原文链接(附件请于原文下方下载):

    用新款ws2812灯带做一个简单的窗花-创意生活论坛-DF创客社区mc.dfrobot.com.cn
    20a7675017c8adb38a79620d5c15860c.png

    作者:屌丝王小明

    很高兴提前拿到了DF即将上架的新品——ws2812灯带,可以让我先试用下。我手上拿的这个就是新的ws2812灯带了,和以前的ws2812的灯带(FIT0352)一样的炫彩,每颗灯珠里都有一个用于控制的ic。每个灯珠都有6个引脚,一边是“+5v”、“Din”、“GND”,另一边的“+5v”、“Do”、“GND”在灯带上其实是连接着下一个灯珠的“Din”一侧的引脚的,就这样,一颗接一颗地连成一串。

    7ccaba61fdcc986104fcfd448c16ab21.gif

    新的ws2812变得更薄更轻了,灯带背面有3m胶,撕下背面的纸后,可以十分方便地贴在窗户、门、桌子、床头、女朋友脸上这些地方。可以在十分美观的情况下,实现我们想要实现的各种炫酷的效果。粘上去后是相当地稳哟。

    287234d1531a8c04ce4e907c962ccd71.gif

    裁剪也更加方便了,轻易地就能将灯珠剪下来,可以实现用单个或几个灯珠,用“点”“线”的形式拼出各种图案和形状。新的ws2812拥有更大的弯曲角度,单粒灯珠就能弯出很大的弧度,更加适用于将其贴在各种曲面甚至球形物体表面。

    4496d7da2aefe7f07c1db6c40fa3c8ab.gif

    15f1ea7924266e2ba2c5a54ff79cac5b.gif

    c508f73e91a0fb5707d3aa2c0824f65a.gif

    马上就是圣诞节了,公司里这一群程序猿和攻城狮,在屏幕面前几十年如一日地禅坐,平日里也没谁说话唠嗑的,都埋头干着自己手上的工作。在这样的寒风萧瑟的时候,又在这样一个寒风萧瑟的环境下的我,决定挺身而出,利用空闲时间来搞点事情,啊哈,就用手上这串新ws2812灯珠。圣诞节还是要有点圣诞节的氛围嘛,于是就把灯珠贴在了窗户上。

    d1c6d258e6d73d4393e1ad063f209ffa.gif

    制作这样的窗花需要用到的材料:

    1. ws2812灯带(我用了169颗)

    2. 微功率太阳能电源管理模块x1

    3. Beetle-ESP32控制器x1

    4. 3.7v锂电池x1

    5. 拨动开关x1

    6. 齿轮变阻器x1

    7. 0.25mm漆包线2米

    8. 0.5mm漆包线5米

    这里先唠叨两句咱们会用到的那个微功率太阳能管理模块(DFR0579),就是下面这个。狠角色呀:

    d29c33319091121352002a8eb5130d39.png

    看完这个模块的资料给我的感觉就是——还有啥它不能的。它可以给3.7v锂电池充电、给3.3v控制器供电、有一对锂电池的输出的焊盘,不仅能用太阳能充电,同时提供一个microusb接口,进行光照不足时的直接充电。大爱呀。

    测量窗户尺寸与灯珠尺寸,做好布局图:

    连线原理图:

    14fe5a397759d7174946ad1f4d29f4f3.png

    Din接beetle的d11口,使用一根0.25mm直径漆包线直接,一根将所有的灯珠上的Din和Do引脚全部串起来。灯珠由锂电池直接供电,由于漆包线负载电流能力有限,所以是分了两组线,上下两行字符分别用了独立的漆包线连接至锂电池正负极。

    制作思路:

    利用太阳能,白天对灯带供电以及对锂电池充电,晚上,就使用锂电池对灯带进行供电,形成一个自给自足的小系统。保持led灯带的常亮。如果所在地区的阳光不够充足,或者阴天情况比较多的话,可以考虑使用呼吸灯或者调低亮度的方式来控制电流大小,来达到灯带保持一直工作的的目的。

    开始制作:

    1.打印一个小模型出来确定灯珠之间的位置摆放,磨刀不误砍柴工:

    d128e3ac0b200e3677d1eac7b05d8d54.png

    d4c37434330fc74259c0d367b0e3ec25.png

    2.贴灯珠上去。你看,是不是一张图就完事儿啦,哈哈哈哈,多么简单。(【转过头去】:我这次实在没有忍住,扯了一回巴子,其实是我贴的时候,太投入,根本停不下来,没时间去拍贴的过程

    ebab14795529ffbe3355ef2e2ab1f147.png

    3.焊漆包线(用尽可能细的漆包线,人站远了看,整个制作就像没有线连着一样,给人感觉简洁):

    f26486bfcbee2ca4a86bdf42e861866c.png

    4.焊各个器件,太阳能板用3m透明双面胶粘,其他的用热熔胶或双面胶粘窗户上:

    21aa47daf791f5fa319085871a7cc1db.png

    5.烧录程序:我使用的是fastled库里的函数,将整个灯带的颜色设置为彩虹色,然后再将其中几个用不着的灯珠设为熄灭。

    最后就完成了:

    b2cf953e84cbfad204838e4b3e28c592.png

    有同事问我,为什么是“hey,merry christmas”。我说,“merry christmas”感觉冷冰冰的,加上一个“hey”之后,是不是就像是在寒冷的冬天,你独自一人走在人烟稀少的路上,突然你的好友不知从哪里冒出来,从后面撞了你一下,然后跟你说“hey,merry christmas”。当你敲了一天代码,累了的时候,一抬头,就看到了窗户上的这句话,是不是会让人感觉到温暖呢。

    展开全文
  • 几个月前,我完成了一次网络综合实验的课设,内容是要设计并实现一个网站下载程序。感觉里面有几个地方挺有意思的,于是在此记录下自己的思路,与大家分享。实验要求网站下载程序可以按照要求下载整个网站的网页,其...

    几个月前,我完成了一次网络综合实验的课设,内容是要设计并实现一个网站下载程序。感觉里面有几个地方挺有意思的,于是在此记录下自己的思路,与大家分享。


    实验要求

    网站下载程序可以按照要求下载整个网站的网页,其原理是分析每个页面中的所有链接,然后根据该链接下载单个文件,并保存下来,采用递归方式进行扫描下载,直到下载页数达到设定好的最大值或者下载层数达到了设定的最大层数才停止。

    主要功能

    (1) 设定站点名称; (2) 设定最大下载页; (3) 设定最大下载层; (4) 设定是否下载多媒体文件(图片); (5) 设定是否下载其他站点网页; (6) 图形化显示。

    思路分析

    实现的思路并不难,首先获取用户输入的网址的 html 文件,然后分析其中的链接和图片并保存到一个列表,接下来对该列表中的链接继续重复这个过程。在下载过程中维持两个计数器,第一个计数器指示着已下载的网页数目,第二个计数器指示着当前下载到了第几层。

    这不就是一个树的层次遍历嘛!每个链接相当于一个子树的根节点,图片就相当于一个叶子节点。

    树的层次遍历需要队列来实现。首先将用户输入的网址入队,然后将其取出,并把该网页中的链接入队(相当于子节点入队),然后取出队头的第一个子节点,将该节点对应的网页中的链接继续入队(相当于把这个子节点的子节点入队)。这时队头的节点就是根节点的第二个子节点了。将其取出,继续进行之前的操作... ...直到达到了两个计数器中的某个的要求。


    接下来就是具体实现的方式了。因为要求图形化显示,所以需要写一个 GUI 界面,这里我在查阅了目前常用的 Python 图形界面库以后,最终选择了自带的 Tkinter 库。虽然之前没写过 Python 的界面,但是参照着官方文档的示例程序和网上的一些例子,也能比较容易地写出来。最终界面如下所示:

    69840689bf5d0d53aa192dafa9341645.png

    当点击开始下载的按钮以后,会触发判断方法,对用户的输入是否合法、网站是否能访问进行验证,如果验证无误才开始正式下载。这部分判断代码比较简单,就不放出来了,感兴趣的同学可以在这里查看源代码。

    然后就是这个树的结构。每个节点都包含 value, children, layer, directory, number 一共五个属性,分别指的是对应的网址、子节点列表、所在的层次、下载后存放的目录,以及当前的序号。这里需要说明存放的目录,因为整个结构是一个树形结构,所以我将下载后的文件也以树形结构保存。定义树节点的具体代码如下:

    class TreeNode(object):
        # 通过__slots__来限制成员变量
        __slots__ = ("value", "children", "layer", "directory", "number")
        # 构造函数
        def __init__(self, value, layer, directory, number):
            self.value = value
            self.children = []
            self.layer = layer
            self.directory = directory
            self.number = number
    
        def get_value(self):
            return self.value
    
        def get_layer(self):
            return self.layer
    
        def get_directory(self):
            return self.directory
        # 返回节点的序号
        def get_number(self):
            return self.number
        # 插入子节点
        def insert_child(self, node):
            self.children.append(node)
    
        def pop_child(self):
            return self.children.pop(0)

    接下来是核心代码部分,首先将根节点入队,然后通过while 循环来进行子节点的入队。之所以需要while,是因为可能根节点和它的页面中的子节点的数目之和还没有达到用户输入的数量,所以需要while 循环让子节点依次出队,并将这些子节点的子节点再次入队。还有一些其他需要注意的地方,在代码的注释进行了说明。

    # 当队列不为空时一直循环,直到入队的节点数目达到用户输入的数量时return 退出
    while treenode_queue:
        # 让队尾的节点出队,下载其 html 文件
        temp = treenode_queue.pop(0)
        re = requests.get(temp.get_value())
        # 注意需要进行编码,否则下载的 html 文件里的中文会乱码
        re.encoding = "utf-8"
        with open(os.path.join(temp.directory, str(temp.number)) + ".html", "w+", encoding="utf-8") as html_file:
            html_file.write(re.text)
        # 判断用户是否勾选下载多媒体文件,主要使用urllib 的urlretrieve()来下载
        if download_img:
            # 用SoupStrainer 类来过滤html 文件里的img 标签
            soup = BeautifulSoup(re.text, "html.parser", parse_only=SoupStrainer('img'))
            count = 1
            print("正在下载", temp.value, "的图片... ...")
            for img in soup:
                if not (img["src"] == ""):
                    if str(img["src"][:2]) == r"//":
                        img["src"] = "https:" + img["src"]
                    img_dir = os.path.join(temp.directory, str(count))
                    if img["src"][-3:] == "png":
                        urllib.request.urlretrieve(img["src"], img_dir + ".png")
                    elif img["src"][-3:] == "gif":
                        urllib.request.urlretrieve(img["src"], img_dir + ".gif")
                    elif img["src"][-3:] == "jpg":
                        urllib.request.urlretrieve(img["src"], img_dir + ".jpg")
                    else:  # 有些图片不带后缀,目前还无法下载下来,比如博客配图
                        print("Failed :", img["src"])
                    count = count + 1
        # 层数的判断出口,当下载的网页层数等于用户输入的层数时,退出
        if layer_count >= int(input_max_layers.get()) + 1:
            download_website_of_queue(*treenode_queue)
            with open(r"D:Download_WebsiteREADME.txt", "w+") as readme_file:
                readme_file.write(get_dir_list(r"D:Download_Website"))
            return
        # 接下来对于出队的节点使用 BeautifulSoup 和 SoupStrainer 来获取链接
        soup = BeautifulSoup(re.text, "html.parser", parse_only=SoupStrainer('a'))
        layer_count = layer_count + 1
        print("----------第" + str(layer_count) + "层----------")
        for each in soup:
            # 对于每个<a>标签中 href 的属性前四个字符是“http”的记录,首先输出其信息,然后构造节点将其入队,并且将其加入上方出队节点的 children 列表
            if each.has_attr("href") and each["href"][:4] == "http":
                website_count = website_count + 1
                print("第" + str(website_count) + "个网站/第" + str(layer_count) +
                      "层:" + each["href"])
                anode = TreeNode(each["href"], layer_count, os.path.join(temp.directory, str(website_count)), website_count)
                temp.insert_child(anode)
                treenode_queue.append(anode)
                if website_count >= int(input_max_pages.get()):
                    download_website_of_queue(*treenode_queue)
                    # 发现下载数目够了的时候,调用函数下载队列中的所有节点的 html 文件,然后生成README文件,记录输出文件夹中的树形结构
                    with open(r"D:Download_WebsiteREADME.txt",
                              "w+") as readme_file:
                        readme_file.write(get_dir_list(r"D:Download_Website"))
                    return

    核心代码中有以下几个点需要注意: - 下载网页时需要进行编码,以避免中文乱码。这里统一编码成 utf-8。 - 注意下载图片时的处理,有的图片网址前面只有 ,没有 https 头,需要我们手动添加。 - 注意函数出口的位置,先判断下载的层数是否已经达到,再进行下一步的操作。 - 注意对两个计数器的操作和判断。

    其中还有两个函数,分别是 download_website_of_queue(),以及 get_dir_list()。前者是用来下载队列中剩下的节点的,内容和核心代码部分比较像。后者用递归的方式产生一个 README 文件,这个文件以树形图的方式呈现了下载后的文件夹结构。这个方法的代码参考了这里。它们的代码如下:

    def download_website_of_queue(*args):
        download_img = False
        if (int(btn_Group1.get()) == 1):
            download_img = True
        # 函数的输入是一个列表,对于队列中的每个节点,下载其html 文件
        for temp in args:
            re = requests.get(temp.get_value())
            re.encoding = "utf-8"
            if not os.path.exists(temp.directory):
                os.makedirs(temp.directory)
            with open(
                    os.path.join(temp.directory, str(temp.number)) + ".html",
                    "w+",
                    encoding="utf-8") as html_file:
                html_file.write(re.text)
    
            if download_img:
                # 以下为下载图片的代码,之前已经给出,此处不再赘述
    
    
    
    # 产生README 说明文件的函数,输入为一个文件夹目录的字符串,输出为该目录下所有文件的树形结构
    # 以字符串的形式输出。主要是通过递归调用来实现。
    BRANCH = '├─'
    LAST_BRANCH = '└─'
    TAB = '│  '
    EMPTY_TAB = '   '
    
    def get_dir_list(path, placeholder=''):
        # 列出输入目录下的文件/文件夹,如果是文件夹,则加入folder_list 列表
        folder_list = [
            folder for folder in os.listdir(path) if os.path.isdir(os.path.join(path, folder))
        ]
        # 列出输入目录下的文件/文件夹,如果是文件,则加入file_list 列表
        file_list = [
            file for file in os.listdir(path) if os.path.isfile(os.path.join(path, file))
        ]
        result = ''
        # 对于文件夹列表中的第一个到倒数第二个元素,添加一个branch,然后递归调用直到
        # 最深的第一个子文件夹,里面只有文件,然后开始加入第一个到倒数第二个文件,
        # 直到添加完该文件夹的最后一个文件,依次类推,直到根目录文件夹的最深的最后
        # 一个子文件夹里的最后一个最后一个文件,递归才结束
        for folder in folder_list[:-1]:
            result += placeholder + BRANCH + folder + 'n'
            result += get_dir_list(os.path.join(path, folder), placeholder + TAB)
        if folder_list:
            result += placeholder + (BRANCH if file_list else
                                     LAST_BRANCH) + folder_list[-1] + 'n'
            result += get_dir_list(
                os.path.join(path, folder_list[-1]),
                placeholder + (TAB if file_list else EMPTY_TAB))
        for file in file_list[:-1]:
            result += placeholder + BRANCH + file + 'n'
        if file_list:
            result += placeholder + LAST_BRANCH + file_list[-1] + 'n'
        return result

    接下来用某博客网站进行测试:

    241dee2f1dde11111e866b38bd5f0e96.png
    存放下载结果的文件夹

    4b7d0ed32344998a3719b90c8f309b1a.png
    README 文件和 html 文件

    主要内容差不多就是这些了。还有一点就是,程序界面上第二个按钮是用来打开对应的文件夹的,这个功能的实现是用 os 模块完成的,只需要一行代码:os.system((r"start explorer D:Download_Website"))。本质上相当于是调用 Windows 控制台的指令来完成的。从这一点出发,也可以产生许多有意思的应用。

    总的来说,这是一个很适合练手的小项目,整个代码加起来还不到 300 行,但可以考察到很多细节方面的东西 ~~~ 虽然我写出来了,但是里面的一些写法、命名之类的还是让人不怎么满意,可能还有一些隐藏的 bug ...但之后我会抽空进行更进一步的完善的!

    最后,给出这个小项目的源代码地址,欢迎交流和讨论 ~~~

    展开全文
  • 下面纸筒做一个简单的小蝴蝶,做法很简单。制作过程:准备材料废纸筒、剪刀、胶、水彩笔,纸板。在纸筒上剪下五个圈圈剪完的样子见下图把里面粘贴修整一下,见下图白色纸板把一个小圆捏成下图形状,粘到纸板正中间...
  • 我们彩色的卡纸做一个冰淇淋,做法非常简单,剪一剪、粘一粘就可以做成。制作过程:准备材料彩色卡纸、剪刀、胶、圆规或瓶盖。把黄色正方形卡纸卷成下图形状,并把边缘粘合剪掉多余的部分,让上边是平的,见下图剪...
  • 导读:制作一个网站网站网页,需要以下多个小步骤(不严格按照时间顺序):1,需求分析 确定网站功能;2,注册公司(这个看网站业...制作一个网站网站网页,需要以下多个小步骤(不严格按照时间顺序):1,需求分析 确定...
  • 大家好,通俗易懂讲营销,我是江湖哥很多剪辑账号的自媒体人,经常会遇到一种情况,在网页浏览中正好看到一个视频,正好是自己需要或者喜欢的,但却不知道如何保存下来。本文就来教你哦,超级简单,几步搞定,傻瓜...
  • 下载说明: 1.再好的作品都不如将来要的作品。在每次的设计当中都能有所收获,才是设计师在web开发中最得益的。 2.本站所有作品均是杨青个人设计。...我在我自己的网站上DIV+CSS的网站,喜欢DI...
  • 什么是前端开发以一个网站为例包括网站设计、前端开发、程序开发等。网站设计就是网站的外观,平面的东西。程序开发也好理解就是功能实现。而前端开发,简单来说,就是把平面效果图转换成网页,把静态转换成动态。它...
  • [HTML5]简单网页本地音乐播放器

    千次阅读 2015-06-10 09:30:40
    一开始问题主要集中在怎么读取本地文件路径,我想肯定可以JS实现去操作本地文件(因为node.js很容易实现读取本地文件,但是原生JS怎么写不太清楚),不过简单一点就这样只能读取一个,我想的是最好是把一个...
  • Html5技术越来越侵蚀着这个互联网,不得不承认,Html5所带来的改变还是很大的,一次偶然的机会,找到了Html5调用电脑摄像头的API,于是想到了做一个网页版大头贴来实现拍照与简单修改的功能,初期...
  • 估计很少人知道HTML5 APIS里有一个window.postMessage API。window.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个...
  • 这是我们v客学院基础班的童鞋都完的一个简单炫酷的特效demo,今天我来带大家最快的速度和最简单的方法制作一个音乐抖动条,大家有兴趣不妨一起来试试~~~~~~~ 这个demo之前我们得有一些html+css3的基础,接...
  • 简单介绍一下这样的好处:【1】python的桌面GUI模块都不太好用,比如pyQT需要额外安装很大的QT,tkinter则相关文档很少,而且对新的控件支持不太好,远不能和html5+css3搭配起来构建的多姿多彩的用户界面相比;...
  • 我们经常看到许多网站或者H5的动画都成了整屏切换的形式,为提高用户体验,会在首屏页面下方放上一个动态的向下箭头来提示用户切换至下一屏。 动态箭头的效果图如下: 那么这种效果是如何实现的呢? 其实非常...
  • 简单介绍一下这样的好处:【1】python的桌面GUI模块都不太好用,比如pyQT需要额外安装很大的QT,tkinter则相关文档很少,而且对新的控件支持不太好,远不能和html5+css3搭配起来构建的多姿多彩的用户界面相比;...
  • 嗨咯 !大家好,这期给大家;...1标记: 标记放在HTML文件的开头,并没有什么实质性的功能,只是一个形式上的标记,但还是希望读者形成一个良好的编写习惯,在HTML文件开头使用标记来做一个形式上的开...
  • Html5技术越来越侵蚀着这个互联网,不得不承认,Html5所带来的改变还是很大的,一次偶然的机会,找到了Html5调用电脑摄像头的API,于是想到了做一个网页版大头贴来实现拍照与简单修改的功能,初期的版本还比较简单,...
  • HTML5 WebSocket 简单实例

    2016-08-10 18:26:26
    在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 例如 网页的 一部分 需要 实时更新数据 如果ajax 请求的话 需要 ...
  • 周二(3个小时): 前两周一直是为了任务而学习,有些本末倒置了,正确的方式应当是先学习,再以...flask是一个网页构建的框架,网页的内容还是靠HTML来实现的,所以今天的 学习内容是:HTML的学习和使用。 这是
  • 最近想弄一个网页,把自己学HTML5过程中的部分DEMO放上去集合,但是,如果就仅仅网页把所有DEMO一个一个排列又觉得太难看了。就想,既然学了canvas,那就来折腾下浏览器,个小小的开场动画吧。 开场动画的...
  • web.py图片上传网页

    2019-10-17 14:52:09
    网页界面属于前端,可以用html HTML5 上传图片文件(含拖拽、预览、上传、美化) HTML 入门笔记 - 初识HTML 如何实现一个简易的图片上传Web Server 这篇博客里的代码可以用 <!DOCTYPE html> <...
  • 现在团队只会个java,做个简单网页,会个PS。我们参加了设计大赛,HTML5创意应用命题,请前辈解答一下问题: 1.HTML5能开发什么,然后对我们这种小白最好开发什么 2.像玩javaEclipse比较好,那做html5什么...
  • 作者:dc lin链接:...简单介绍一下这样的好处:【1】python的桌面GUI模块都不太好用,比如pyQT需要额外安装很大的QT,tkinter则相关文档很少,而且对新的控件支持不太好,远不能和html5+css3...
  • 对于Html5直播网站源码实现视频录制,可以使用 webRTC(Web Real-Time Communication),这是一个支持网页浏览器进行实时语音对话或视频对话的技术,缺点是只在 PC 的Chrome上支持较好,但移动端支持不太理想。...
  • 近一两年,HTML5在中国很火,也出了不少HTML5工具和模板。...例如,iPhone4、iPhone5、iPhone6、PC、iPad访问同一个H5网页,都能打开一个适合该设备的一个HTML5页面,不留白边,不变形。 由于设备分辨...
  • 简单介绍一下这样的好处:【1】python的桌面GUI模块都不太好用,比如pyQT需要额外安装很大的QT,tkinter则相关文档很少,而且对新的控件支持不太好,远不能和html5+css3搭配起来构建的多姿多彩的用户界面相比;...
  • 作者:dc lin链接:...简单介绍一下这样的好处:【1】python的桌面GUI模块都不太好用,比如pyQT需要额外安装很大的QT,tkinter则相关文档很少,而且对新的控件支持不太好,远不能和html5+css3...

空空如也

空空如也

1 2 3 4 5 ... 17
收藏数 323
精华内容 129
关键字:

用html5做一个简单网页