在博客网站发布了一些博客。总共有四十多篇。现在查看这些博客需要翻页,不是很方便。
想要弄出一个列表,把标题和链接展示出来。如果手动去复制粘贴,耗时耗力,也不够自动化。
有没有什么自动化的方法呢?想到以前用python做过抓取的功能。

开发环境:

  • Python3.7
  • PyCharm 2018.3.7 (Community Edition)
  • macOS 11.4

安装scrapy

我们主要使用的框架是scrapy,官网 https://scrapy.org/

如果需要换用清华的pip源,执行下面的命令

1
2
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

根据官网文档进行安装

1
pip3 install Scrapy

分析目标网页

目标网页的部分html如下

1
2
3
4
5
<div class="blog-menu blog-menu-noImg">
<div class="blog-menu-header blog_no_attachment">
<a id="portal_usercenter_2_usernew_blog_title_cus_i_0" href="/blogs/301790" class="common-blog-title" title="Kotlin协程取消与超时" target="_blank" style="margin-left: 0px; color: rgb(51, 51, 51);"><span class="blog-title-wrap" style="overflow-wrap: break-word;">
Kotlin协程取消与超时
</span></a></div>

需要提取的是标题(title)和网址(url)。

shell工具

使用scrapy shell工具来分析一下目标网页

1
scrapy shell 'https://bbs.huaweicloud.com/community/usersnew/id_1606985929124732/page_1'

windows下的url需要用双引号

可以看到如下的运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2021-10-13 09:46:47 [asyncio] DEBUG: Using selector: KqueueSelector
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x7fd889766990>
[s] item {}
[s] request <GET https://bbs.huaweicloud.com/community/usersnew/id_1606985929124732/page_1>
[s] response <200 https://bbs.huaweicloud.com/community/usersnew/id_1606985929124732/page_1>
[s] settings <scrapy.settings.Settings object at 0x7fd88987bb50>
[s] spider <DefaultSpider 'default' at 0x7fd889c7b710>
[s] Useful shortcuts:
[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s] fetch(req) Fetch a scrapy.Request and update local objects
[s] shelp() Shell help (print this help)
[s] view(response) View response in a browser
2021-10-13 09:46:48 [asyncio] DEBUG: Using selector: KqueueSelector

尝试用scrapy的css()方法来提取元素,操作它的response变量

例如提取div.blog-menu

1
2
3
4
In [2]: response.css('div.blog-menu')
Out[2]:
[<Selector xpath="descendant-or-self::div[@class and contains(concat(' ', normalize-space(@class), ' '), ' blog-menu ')]" data='<div class="blog-menu blog-menu-noImg"><'>,
...

可以得到页面上所有满足要求的元素。拿到的元素是Selector,可以再进行下一步的操作。

用for循环读取提取到的元素,在此基础上,去抓取中文标题

1
2
3
4
5
6
7
8
9
10
11
12
13
In [6]: for item in response.css('div.blog-menu'):
...: print(item.css('a.common-blog-title::attr(title)').extract()[0])
...:
Kotlin协程取消与超时
Android Kotlin协程入门
Kotlin协程基础
Kotlin协程入门
Android View post 方法
Android Activity 传递Parcelable对象
Android Handler,Looper与MessageQueue使用与分析
Android线程池使用介绍
Java线程介绍
使用ECharts绘制网址径向树状图

a.common-blog-title::attr(title)表示的是我们要查找<a>里面的内容,指定class为common-blog-title,并且查找title属性。
css().extract()将结果列表提取出来。最后得到我们关心的信息。

获取url同理,把提取部分修改为::attr(href)

代码

前面用shell工具进行分析,我们了解提取目标信息的方法。接下来写Python代码。

在合适的地方新建一个scrapy工程

1
scrapy startproject blog

可以得到一系列文件
1
2
3
4
5
6
7
8
9
blog
spiders
.gitignore
__init__.py
__init__.py
items.py
middlewares.py
pipelines.py
settings.py

spiders目录里新建一个文件Hw.py,它就是我们的“蜘蛛”。完整代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import json

import scrapy


def cmp(item):
return item['page']


class HwBlog(scrapy.Spider):
"""
在最外层的blog目录进行操作
scrapy crawl hw
"""
name = "hw" # scrapy crawl hw
res_list = []
req_count = 0
total_url = 5

def start_requests(self):
self.res_list = []
urls = []
base_url = 'https://bbs.huaweicloud.com/community/usersnew/id_1606985929124732/page_'
for i in range(1, self.total_url + 1):
urls.append(base_url + str(i))

for url in urls:
yield scrapy.Request(url=url, callback=self.parse)

def parse(self, response):
self.req_count = self.req_count + 1
page = response.url.split("_")[-1]

for item in response.css('div.blog-menu'):
title_se = item.css("a.common-blog-title")
blog_url = 'https://bbs.huaweicloud.com' + (title_se.css("::attr(href)").extract()[0])
blog_title = title_se.css("::attr(title)").extract()[0]
self.res_list.append({"title": blog_title, "url": blog_url, "page": page})

json_res = json.dumps(self.res_list, ensure_ascii=False)
print(json_res)

res_file_path = 'raw/hw.json'
with open(res_file_path, 'w+') as f:
f.write(json_res)

if self.req_count < self.total_url:
return
res_md = 'raw/hw.md'
self.res_list.sort(key=cmp) # 用页码来排序
with open(res_md, 'w+') as f:
f.writelines('## 华为云社区')
f.write('\n博客数量:' + str(len(self.res_list)) + '\n')
for d in self.res_list:
f.write('\n')
f.write('- [' + d['title'] + '](' + d['url'] + ')')

要启动的话,输入以下命令

1
scrapy crawl hw

接下来简单看一下代码

变量

name是蜘蛛的名字。我们启动的时候会用到它。这个名字由我们自己定义。

res_list是暂存结果的列表。把获取到的结果暂时存放在里面。

total_url表示一共要爬几个页面

req_count表示请求次数

start_requests(self)

在这个方法里进行一些配置工作。比如配置目标url。
我们是预先知道有5个页面,所以配置了5个url。

scrapy.Request(url=url, callback=self.parse)构建请求

parse是回调函数,请求成功后走这个方法。

parse(self, response)

请求成功后,结果保存在response中。前面用shell进行的操作,可以直接写成python代码。

当请求次数达到目标后,开始写文件。

小结

这是Python scrapy的一次简单应用。

安装scrapy,分析目标网页。把获取到的数据整理后写成markdown文件。

视频在这里