内容基于 https://github.com/96chh/crawl-zsxq 进行的优化,主要优化内容在于,翻页时间的处理、大段空白处理、评论抓取、超链接处理等。
涉及到隐私问题,这里我们以免费星球「万人学习分享群」为爬取对象。
过程分析 模拟登录爬取的是网页版知识星球,https://wx.zsxq.com/dweb/。
这个网站并不是依靠 cookie 来判断你是否登录,而是请求头中的 Authorization 字段。
所以,需要把 Authorization,User-Agent 换成你自己的。(注意 User-Agent 也要换成你自己的)
一般来说,星球使用微信扫码登录后,可以获取到一个 Authorization,这个歌有效期很长反正,真的很长。
headers = { 'Authorization': 'C08AEDBB-A627-F9F1-1223-7E212B1C9D7D', 'x-request-id': "7b898dff-e40f-578e-6cfd-9687a3a32e49", 'accept': "application/json, text/plain, */*", 'host': "api.zsxq.com", 'connection': "keep-alive", 'referer': "http://wx.zsxq.com/dweb/", 'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", } 页面内容分析登录成功后,一般我习惯右键、检查或者查看源代码。
但是这个页面比较特殊,它不把内容放到当前地址栏 URL 下,而是通过异步加载(XHR),只要找对接口就可以了。
精华区的接口:https://api.zsxq.com/v1.10/groups/2421112121/topics?scope=digests&count=20
这个接口是最新 20 条数据的,还有后面数据对应不同接口,这就是后面要说的翻页。
制作 PDF 电子书需安装模块:
wkhtmltopdf,用作导出 PDF,安装完成后可用命令生成 PDF,例如:wkhtmltopdf google.pdf
pdfkit,是 python 对 wkhtmltopdf 调用的封装,支持 URL、本地文件、文本内容到 PDF 的转换,实际转换还是最终调用 wkhtmltopdf 命令
本来精华区是没有标题的,我把每个问题的前 6 个字符当做标题,以此区分不同问题。
爬取图片很明显,在返回的数据中的 images 键就是图片,只需提取 large 的,即高清的 url 即可。
关键在于将图片标签 img 插入到 HTML 文档。我使用 BeautifulSoup 操纵 DOM 的方式。
需要注意的是,有可能图片不止一张,所以需要用 for 循环全部迭代出来。
if content.get('images'): soup = BeautifulSoup(html_template, 'html.parser') for img in content.get('images'): url = img.get('large').get('url') img_tag = soup.new_tag('img', src=http://www.likecs.com/url) soup.body.append(img_tag) html_img = str(soup) html = html_img.format(title=title, text=text) 制作精美 PDF通过 css 样式来控制字体大小、布局、颜色等,详见 test.css 文件。
再将此文件引入到 options 字段中。
options = { "user-style-sheet": "test.css", ... } 难点分析 翻页逻辑爬取地址是:{url}?scope=digests&count=20&end_time=2018-04-12T15%3A49%3A13.443%2B0800
路径后面的 end_time 表示加载帖子的最后日期,以此达到翻页。
这个 end_time 是经过 url 转义了的,可以通过 urllib.parse.quote 方法进行转义,关键是找出这个 end_time 是从那里来的。
经过我细细观察发现:每次请求返回 20 条帖子,最后一条贴子就与下一条链接的 end_time 有关系。
例如最后一条帖子的 create_time 是 2018-01-10T11:49:39.668+0800,那么下一条链接的 end_time 就是 2018-01-10T11:49:39.667+0800,注意,一个 668,一个 667 , 两者相差,于是我们便得到了获取 end_time 的公式:
end_time = create_time[:20]+str(int(create_time[20:23])-1)+create_time[23:]不过事情没有那么简单,因为上一个 create_time 有可能是 2018-03-06T22%3A29%3A59.000%2B0800,-1 后出现了负数!
由于时分秒都有出现 0 的可能,看来最好的方法是,若出现 000,则利用时间模块 datetime 获取 create_time 的上一秒,然后在拼接 999。
# int -1 后需要进行补 0 处理,test_str.zfill(3) end_time = create_time[:20]+str(int(create_time[20:23])-1).zfill(3)+create_time[23:] # 时间出现整点时需要特殊处理,否则会出现 -1 if create_time[20:23] == '000': temp_time = datetime.datetime.strptime(create_time, "%Y-%m-%dT%H:%M:%S.%f+0800") temp_time += datetime.timedelta(seconds=-1) end_time = temp_time.strftime("%Y-%m-%dT%H:%M:%S") + '.999+0800' end_time = quote(end_time) next_url = start_url + '&end_time=' + end_time处理过程有点啰嗦,原谅我时间后面的 000 我没找到直接处理的办法,只能这样曲线救国了。
判断最后一页翻页到最后返回的数据是:
{"succeeded":true,"resp_data":{"topics":[]}}故以 next_page = rsp.json().get('resp_data').get('topics') 来判断是否有下一页。
评论爬取发现评论里也有很多有用的内容,评论的格式如下:
{ "comment_id": 15118288421852, "create_time": "2018-08-16T16:19:39.216+0800", "owner": { "user_id": 1484141222, "name": "xxx", "alias": "xxx", "avatar_url": "https://images.xxx" }, "text": "他这个资源不做投资才傻", "likes_count": 0, "rewards_count": 0, "repliee": { "user_id": 484552118, "name": "Kiwi", "avatar_url": "https://images.zsxqxxxTRQKsci9Q=" } }