文档章节

一步步教你打造微信公众号文章爬虫(2)-下载网页

o
 osc_g8254g7s
发布于 2019/08/19 20:05
字数 2830
阅读 12
收藏 0

钉钉、微博极速扩容黑科技,点击观看阿里云弹性计算年度发布会!>>>

 

一步步教你打造微信公众号文章爬虫(2)-下载网页

 

书接上文,今天一起来学习把网页版文章下载到本地电脑上。

前面讲过,请求网页的流程是浏览器先向服务器请求html,服务器返回html,浏览器分析这个html,发现html中还需要一堆的js,css,图片,然后浏览器再去下载这些文件,最终组装成一个完整的html页面。


所以,第一步,要把这个html下载下来。

是时候请出大家期待已久的python了,我在讲解的过程中只列出核心代码,完整代码会列在文章最后,所以强烈建议先把整篇文章看完了再动手自己敲代码。其他文章也是相同的逻辑,以后不再重复。

需要用到一个鼎鼎大名的第三方库 requests ,用它来模拟浏览器给微信服务器发送请求和接收请求。

那么发送的请求中都要包含什么内容呢?

上文介绍chrome开发者工具时提过这个问题,奥秘在Headers这个标签中,见下图,理论上来讲chrome浏览器发送了什么我们的最好就原样照着用python发送什么,即把下图所示的General 和 Request Headers 块中的参数全都发送出去。但多数时候并不需要这样,特别是对于get请求,一般只需要少数几个参数即可,但是请注意User-Agent这一项一定要改得跟chrome一样。其他细节不再多述,过后您操作的多了自然会明白。

 

简单的注释会直接在代码中列出,复杂的会在代码后面用文字再解释,另外本文是一份原稿发布在多个平台,可能有的平台显示代码会有缩进错乱的问题,当你发现运行代码出错时请秉持“尽信书不如无书”的批判态度。

 

#下面这一行一定别忘了
import requests
#定义一个保存文件的函数
def SaveFile(fpath,fileContent):
    with open(fpath, 'w', encoding='utf-8') as f:
            f.write(fileContent)
#定义一个下载url网页并保存的方法
def DownLoadHtml(url):
    #构造请求头    
    headers = {
                 'User-Agent':'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
                 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 
                 'Connection':'keep-alive', 
                 'Accept-Language':'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3'
              }     
#模拟浏览器发送请求     
    response = requests.get(url,headers = headers)    
    if response.status_code == 200:#返回码为200表示正常返回
        htmltxt = response.text #网页正文
        print(htmltxt)        
        return htmltxt     
    else:        
        return None 
    
    
if __name__ == "__main__": 
    url = "https://mp.weixin.qq.com/s/tK6jYTTtl8jQI86fysvj4g"
    htmlstr = DownLoadHtml(url)
    savepath="c:/vWeChatFiles/test.html" #先手动在C盘下新建vWeChatFiles文件夹
    SaveFile(savepath,htmlstr)


运行上面代码之前,请先手动在C盘下新建vWeChatFiles文件夹,本项目之后下载的所有文件都放在此目录下,请注意这里是保存下载的网页文件的,而你的python源代码可以放在其他目录下。

运行上面的代码,你将看到屏幕刷刷得显示一堆网页源代码,并且在c:/vWeChatFiles文件夹下生成一个test.html文件。

下面,来看这个下载下来的test.html能否正常工作,请先打开chrome,按F12,弹出开发者工具,在最上面一行标签中切换到 Network,然后在chrome 的网页中输入C:/vWeChatFiles/test.html,回车。可能需要等几十秒,看到了刚才我们下载的网页。

同时chrome开发者工具显示如下图所示


有许多行是红色的,表示这些文件下载失败了,刚才之所以等了好久网页才显示出来就是因为浏览器在尝试等待这些文件的下载,等过了一段时间还是没有等到对的人,它说那就算了吧,我只把接收到的内容显示出来。


所以,仔细查看加载出来的test.html,文字显示正常,格式也正常,但是没图片啊。其实这个图片没显示的原因比较复杂,先不展开讲。

在test.html网页的空白处右键,点"查看网页源代码",chrome中会打开一个新窗口,显示这个网页的原始源代码,此处的源代码,跟用记事本打开test.html文件所看到的源代码是一样的。

但是,这些原始代码跟开发者工具中 Elements 中显示的,即下图右侧显示的格式化的网页代码可能不一样。比如下图箭头所指位置的“1周前”,虽然你在右侧的源代码中也看到了“1周前”,但是在最原始的代码中此处并没有“1周前”。
其实右侧显示的代码是原始源代码被浏览器打开之后运行了js程序计算出来的网页结构代码。搞明白两种源代码的不同之处,可以帮初学者减少许多迷惑。


然而奇怪的是,看源代码对应的<img />标签中竟然没有 src 属性,而开发者工具中的<img /> 中的src是一堆奇怪的文字,并不像一个网址。(不过至少此处再次印证了上文我说的两处的源代码有可能并不一致)


稍延伸一下,上图所示的src中的一串字符其实是表示一张图片,一张用base64文本表示的简单图片,有兴趣的朋友可以自行百度,顺便告诉你一个好消息:百度竟然不要钱。

这些都不管了,直接把src改成正确的图片网址吧。可是,图片网址在哪里呢?请再仔细看上面的一张图, data-src 好像是一个网页,把这一串网页复制出来,粘贴到浏览器地址栏中打开,神奇的事情发生了:竟然是文章中的一张高清无码大图。那我们就有理由相信,这就是原本此处要显示的图片的网址,可它为啥藏着掖着,而不是直接给src设置好呢?这是个好问题,知道答案的朋友请在文章下方留言,我送你上墙。

下面,就把此网页中所有图片标签<img /> 中的 data-src 的网址赋给src,需要用到另一个优秀的第三方库 BeautifulSoup,它可以解析网页中的元素,并对它们进行修改、增加、删除等操作。下面只列出了改变过、需要重点查看的部分,并不是完整代码,所以你直接保存复制是运行不起来的,但如果加在上面给出的源代码的后面就能跑起来(当然要小改一些地方,再次提醒读者需要有一点编程基础)。

#新添加一个引用
from bs4 import BeautifulSoup
#读取文件
def ReadFile(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        all_the_text = f.read()
    return all_the_text

#修改网页中图片的src,使图片能正常显示
def ChangeImgSrc(htmlsource):
    bs = BeautifulSoup(htmlsource,"lxml") #由网页源代码生成BeautifulSoup对象,第二个参数固定为lxml
    imgList = bs.findAll("img") #找出网页中所有的<img 标签
    for img in imgList:
        originalURL = ""  # 定义一个变量保存图片真实url
        if "data-src" in img.attrs: #防止有的<img 标签中可能没有data-src而出错
            originalURL = img.attrs['data-src']
        elif "src" in img.attrs: #如果有src则提取出来
            originalURL = img.attrs['src']
        else:
            originalURL = ""
        if originalURL.startswith("//"):#如果url以//开始,则需要添加http:
            originalURL = "http:" + originalURL
        img.attrs["src"] = originalURL
    return str(bs) #将BeautifulSoup对象再置换为字符串,用于保存

if __name__ == "__main__":
    url = "https://mp.weixin.qq.com/s/tK6jYTTtl8jQI86fysvj4g"
    htmlstr = DownLoadHtml(url)
    htmlstr = ChangeImgSrc(htmlstr)
    savepath="c:/vWeChatFiles/test2.html" #先手动在C盘下新建vWeChatCrawl文件夹
    SaveFile(savepath,htmlstr)

注意第21行,有的图片会以 //http://res.wx.qq.com开头,需要换成常见的http://res.wx.qq.com 形式开头,否则本地打开网页时这些图片会无法正常加载。至于为什么会有 // 开头的网址您可以自行百度。

C:\vWeChatFiles文件夹下会有一个test2.html文件,在chrome中打开,网页可以正常显示了,但是开发者工具中依然显示有许多请求是红字状态,先不用管。

接下来的问题是,虽然图片可以在网页中显示了,但src是一个网址,图片是在每次打开网页时从远程服务器下载的,并没有保存到本地,如果远程文章被删了图片就没法看了。

接下来我们要做的是:把图片下载到本地,并在html中把图片的src指向本地图片的位置。

主要代码如下,需要先在vWeChatFiles目录下新建一个images文件夹

#将图片从远程下载保存到本地
#url:图片网址
#savepath:本地保存路径
def DownImg(url,savepath):
    r = requests.get(url)
    with open(savepath, 'wb') as f:
        f.write(r.content)

#修改网页中图片的src,使图片能正常显示
def ChangeImgSrc(htmltxt):
    bs =BeautifulSoup(htmltxt,"lxml") #由网页源代码生成BeautifulSoup对象,第二个参数固定为lxml
    imgList = bs.findAll("img") #找出网页中所有的图片
    imgindex = 0 #图片编号,不同图片要保存为不同的名称
    for img in imgList:
        imgindex += 1
        originalURL = ""  # 定义一个变量用以保存图片真实url
        if "data-src" in img.attrs:#有的<img 标签中可能没有data-src
            originalURL = img.attrs['data-src']
        elif "src" in img.attrs:#如果没有data-src但有src则提取src
            originalURL = img.attrs['src']
        else: #啥都没有则src为空吧
            originalURL = ""
        if originalURL.startswith("//"):#如果url以//开始,则需要添加http:
            originalURL = "http:" + originalURL
        if len(originalURL) > 0: #有图片网址则下载该图片
            print(originalURL)
            if "data-type" in img.attrs:
                imgtype = img.attrs["data-type"] #文件的扩展名
            else:
                imgtype = "png" #不知道图片扩展名的情况下默认为png
            imgname = str(imgindex)+"."+imgtype #形如 1.png的图片名
            imgsavepath = "c:/vWeChatFiles/images/"+imgname #图片保存目录,后续要改成相对地址
            DownImg(originalURL,imgsavepath) #下载图片
            img.attrs["src"] = "images/" + imgname #网页中图片的相对路径
        else :
            img.attrs["src"] = ""
    return str(bs) #将BeautifulSoup对象再置换为字符串,用于保存


至此,网页保存到本地基本完成,其实还有几个小问题,比如:


引用样式表时也是用到了 // 开头的网址,聪明的你仿照图片的修改方法去修改这里应该不是问题,就当是小小的练习了。

又比如:开发者工具中看到许多 js 文件加载失败,但并不影响网页显示,且过后会专门处理js,所以先不用管。

请注意,本示例代码为了方便大家理解,许多地方用了硬编码,且都堆在一个文件中,这不是最佳方式,甚至可以说是糟糕的方式,但在现阶段代码不多的情况下这不是啥问题,大家先把注意力集中在功能的实现上即可。

本文会同步在多个平台,由于公众号等平台发文后不可修改,所以勘误和难点解释请注意查看文后留言,也可以点击"阅读原文"查看我的个人博客版。关注本号,查看后续更新。


本文仅用于技术学习交流,请勿用于非法用途,由此引起的后果本作者概不负责。

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
一步步教你打造微信公众号文章爬虫(1)-综述

本系列我将与大家一起学习批量下载任意公众号所有历史文章。 争取讲明白,源代码也会随着教程逐步放出来,但是不喜欢伸手党和不过如此党(凡事都说虽然我不会但我觉得不难的人)。 用户需要有一...

osc_575sntwo
2019/07/29
4
0
一步步教你打造微信公众号文章爬虫(3)-批量下载

本文为本专题第3篇,如果你基础不错的话只看这一篇的讲解及源代码应该就能轻松实现批量下载所有文章了,基础稍差的可以先看一下前两篇,只要你有点基础,有点耐心,八成朋友都至少能照葫芦画...

osc_g8254g7s
2019/08/19
3
0
Python实现微信自动回复和群聊助手

长按关注公众号 本文分享自微信公众号 - 月小水长(inspurer)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。...

ygdxt
2018/12/02
0
0
谈谈本号的近期工作和规划

本文分享自微信公众号 - 月小水长(inspurer)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。...

ygdxt
2019/12/18
0
0
2019:一名白帽的成长史总集篇

本文分享自微信公众号 - 一名白帽的成长史(monster-liuzhi)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。...

一名白帽的成长史
01/03
2
0

没有更多内容

加载失败,请刷新页面

加载更多

未捕获ReferenceError:未定义$? - Uncaught ReferenceError: $ is not defined?

问题: How come this code throws an 此代码如何引发 Uncaught ReferenceError: $ is not defined 未捕获的ReferenceError:未定义$ when it was OK before? 以前什么时候可以? $(document......

javail
39分钟前
12
0
263. Ugly Number

题目: 263. Ugly Number 题目地址:https://leetcode.com/problems/ugly-number/ Write a program to check whether a given number is an ugly number. Ugly numbers are positive numbers......

JiaMing
今天
60
0
HCIA_ARP01

ARP(地址解析协议) eNSP 常用路由器:AR2220 常用交换机:S5700、S3700 常用终端:PC、MCS(主播服务器) 设备连线:Copper(以太网用到的双绞线)、Serial(串口线,2SA接口)、Auto(自动连...

创业789
今天
25
0
如何在Rails 4中使用问题 - How to use concerns in Rails 4

问题: The default Rails 4 project generator now creates the directory "concerns" under controllers and models. 默认的Rails 4项目生成器现在在控制器和模型下创建目录“Concer”。 ......

fyin1314
今天
19
0
【LeetCode】 57 括号生成

题目: 解题思路: https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/ 代码: import java.util.ArrayList;import java.util.List;publ......

JaneRoad
昨天
18
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部