前言
最近业余在做一个基于.NET Core的搜索项目,奈何基层代码写好了,没有看起来很华丽的数据供测试。很巧的也是博客搜索,于是乎想到了博客园。C#也能做做页面数据抓取的,不过在博客园看到的大部分都是python实现,所以就临时想了一下看看python到底是什么东东,不看基础语法,不看语言功能,直接上代码,哪里不会搜哪里。代码完成总共用时大概4个小时,其中搭建环境加安装BeautifulSoup大概1个小时。解析HTML用时间最多了,边看demo边解析,大概2个小时,剩下的时间就是调试加保存数据了。
环境搭建
既然用python,那么自然少不了语言环境。于是乎到官网下载了3.5版本的。安装完之后,随机选择了一个编辑器叫PyCharm,话说python编辑器还真挺多的。由于本人是小白,所以安装事项不在过多赘述。
建好项目,打开编辑器,直接开工。本来之前用C#写的时候,大体思路就是获取网页内容,然后正则匹配。后来发现网上的帖子也很多。不过在搜索过程中发现,不建议用正则来匹配HTML。有正好我的正则不太好,所以我就搜了一下HTML解析工具,果不其然,人家都做好了,直接拿来用吧。没错就是这个东东:BeautifulSoup 。安装也很简单,不过中间出了个小插曲,就是bs4没有。继续搜,然后需要用pip安装一下就好了。(当然我并不知道ps4和pip是什么鬼)
思路分析
博客吗,我当然就对准了博客园,于是乎,进入博客园首页,查看请求。
发送请求
当然我不知道python是怎么进行网络请求的,其中还有什么2.0和3.0的不同,中间曲曲折折了不少,最终还是写出了最简单的一段请求代码。
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
|
import
urllib
.
parse
import
urllib
.
request
# params CategoryId=808 CategoryType=SiteHome ItemListActionName=PostList PageIndex=3 ParentCategoryId=0 TotalPostCount=4000
def
getHtml
(
url
,
values
)
:
user_agent
=
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36'
headers
=
{
'User-Agent'
:
user_agent
}
data
=
urllib
.
parse
.
urlencode
(
values
)
response_result
=
urllib
.
request
.
urlopen
(
url
+
'?'
+
data
)
.
read
(
)
html
=
response_result
.
decode
(
'utf-8'
)
return
html
#获取数据
def
requestCnblogs
(
index
)
:
print
(
'请求数据'
)
url
=
'http://www.cnblogs.com/mvc/AggSite/PostList.aspx'
value
=
{
'CategoryId'
:
808
,
'CategoryType'
:
'SiteHome'
,
'ItemListActionName'
:
'PostList'
,
'PageIndex'
:
index
,
'ParentCategoryId'
:
0
,
'TotalPostCount'
:
4000
}
result
=
getHtml
(
url
,
value
)
return
result
|
其实博客园这个请求还是挺标准的,哈哈正好适合抓取。因为他返回的就是一段html。(如果返回json那不是更好。。。。)
数据解析
上文已经提到了,用到的是BeautifulSoup,好处就是不用自己写正则,只要根据他的语法来写就好了,在多次的测试之后终于完成了数据的解析。先上一段HTML。然后在对应下面的代码,也许看起来更轻松一些。
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
|
<
div
class
=
"post_item"
>
<
div
class
=
"digg"
>
<
div
class
=
"diggit"
onclick
=
"DiggPost('hyper-xl',6417741,281238,1)"
>
<
span
class
=
"diggnum"
id
=
"digg_count_6417741"
>
1
<
/
span
>
<
/
div
>
<
div
class
=
"clear"
>
<
/
div
>
<
div
id
=
"digg_tip_6417741"
class
=
"digg_tip"
>
<
/
div
>
<
/
div
>
<
div
class
=
"post_item_body"
>
<
h3
>
<
a
class
=
"titlelnk"
href
=
"http://www.cnblogs.com/hyper-xl/p/6417741.html"
target
=
"_blank"
>
Python
字符串格式化
<
/
a
>
<
/
h3
>
<
p
class
=
"post_item_summary"
>
<
a
href
=
"http://www.cnblogs.com/hyper-xl/"
target
=
"_blank"
>
<
img
width
=
"48"
height
=
"48"
class
=
"pfs"
src
=
"//pic.cnblogs.com/face/795666/20160421231717.png"
alt
=
""
/
>
<
/
a
>
转载请注明出处
Python2
.
6
+
增加了
str
.
format函数,用来代替原有的
'%'操作符
。它使用比
'%'更加直观、灵活。下面详细介绍一下它的使用方法。
下面是使用
'%'的例子
:
格式很像
C语言的
printf是不是?由于
'%'是一个操作符,只能在左右
两边各放一个参数,因此右边多个值需要用元组或
.
.
.
<
/
p
>
<
div
class
=
"post_item_foot"
>
<
a
href
=
"http://www.cnblogs.com/hyper-xl/"
class
=
"lightblue"
>新月的力量
_141
<
/
a
>
发布于
2017
-
02
-
19
23
:
07
<
span
class
=
"article_comment"
>
<
a
href
=
"http://www.cnblogs.com/hyper-xl/p/6417741.html#commentform"
title
=
""
class
=
"gray"
>
评论
(
0
)
<
/
a
>
<
/
span
>
<
span
class
=
"article_view"
>
<
a
href
=
"http://www.cnblogs.com/hyper-xl/p/6417741.html"
class
=
"gray"
>
阅读
(
138
)
<
/
a
>
<
/
span
>
<
/
div
>
<
/
div
>
<
div
class
=
"clear"
>
<
/
div
>
<
/
div
>
|
通过上文的HTML代码可以看到几点。首先每一条数据都在 div(class=”post_item”)下。然后 div(“post_item_body”)下有用户信息,标题,链接,简介等信息。逐一根据样式解析即可。代码如下:
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
|
from
bs4
import
BeautifulSoup
import
request
import
re
#解析最外层
def
blogParser
(
index
)
:
cnblogs
=
request
.
requestCnblogs
(
index
)
soup
=
BeautifulSoup
(
cnblogs
,
'html.parser'
)
all_div
=
soup
.
find_all
(
'div'
,
attrs
=
{
'class'
:
'post_item_body'
}
,
limit
=
20
)
blogs
=
[
]
#循环div获取详细信息
for
item
in
all_div
:
blog
=
analyzeBlog
(
item
)
blogs
.
append
(
blog
)
return
blogs
#解析每一条数据
def
analyzeBlog
(
item
)
:
result
=
{
}
a_title
=
find_all
(
item
,
'a'
,
'titlelnk'
)
if
a_title
is
not
None
:
# 博客标题
result
[
"title"
]
=
a_title
[
0
]
.
string
# 博客链接
result
[
"href"
]
=
a_title
[
0
]
[
'href'
]
p_summary
=
find_all
(
item
,
'p'
,
'post_item_summary'
)
if
p_summary
is
not
None
:
# 简介
result
[
"summary"
]
=
p_summary
[
0
]
.
text
footers
=
find_all
(
item
,
'div'
,
'post_item_foot'
)
footer
=
footers
[
0
]
# 作者
result
[
"author"
]
=
footer
.
a
.
string
# 作者url
result
[
"author_url"
]
=
footer
.
a
[
'href'
]
str
=
footer
.
text
time
=
re
.
findall
(
r
"发布于 .+? .+? "
,
str
)
result
[
"create_time"
]
=
time
[
0
]
.
replace
(
'发布于 '
,
''
)
comment_str
=
find_all
(
footer
,
'span'
,
'article_comment'
)
[
0
]
.
a
.
string
result
[
"comment_num"
]
=
re
.
search
(
r
'\d+'
,
comment_str
)
.
group
(
)
view_str
=
find_all
(
footer
,
'span'
,
'article_view'
)
[
0
]
.
a
.
string
result
[
"view_num"
]
=
re
.
search
(
r
'\d+'
,
view_str
)
.
group
(
)
return
result
def
find_all
(
item
,
attr
,
c
)
:
return
item
.
find_all
(
attr
,
attrs
=
{
'class'
:
c
}
,
limit
=
1
)
|
上边一堆代码下来,着实花费了我不少时间,边写边调试,边百度~~不过还好最终还是出来了。等数据都整理好之后,然后我把它保存到了txt文件里面,以供其他语言来处理。本来想写个put直接put到ElasticSearch中,奈何没成功。后边在试吧,毕竟我的重点只是导数据,不在抓取这里。
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
|
import
match
import
os
import
datetime
import
json
def
writeToTxt
(
list_name
,
file_path
)
:
try
:
#这里直接write item 即可,不要自己给序列化在写入,会导致json格式不正确的问题
fp
=
open
(
file_path
,
"w+"
,
encoding
=
'utf-8'
)
l
=
len
(
list_name
)
i
=
0
fp
.
write
(
'['
)
for
item
in
list_name
:
fp
.
write
(
item
)
if
i
<
l
-
1
:
fp
.
write
(
',\n'
)
i
+=
1
fp
.
write
(
']'
)
fp
.
close
(
)
except
IOError
:
print
(
"fail to open file"
)
#def getStr(item):
# return json.dumps(item).replace('\'','\"')+',\n'
def
saveBlogs
(
)
:
for
i
in
range
(
1
,
2
)
:
print
(
'request for '
+
str
(
i
)
+
'...'
)
blogs
=
match
.
blogParser
(
i
,
5
)
#保存到文件
path
=
createFile
(
)
writeToTxt
(
blogs
,
path
+
'/blog_'
+
str
(
i
)
+
'.json'
)
print
(
'第'
+
str
(
i
)
+
'页已经完成'
)
return
'success'
def
createFile
(
)
:
date
=
datetime
.
datetime
.
now
(
)
.
strftime
(
'%Y-%m-%d'
)
path
=
'/'
+
date
if
os
.
path
.
exists
(
path
)
:
return
path
else
:
os
.
mkdir
(
path
)
return
path
result
=
saveBlogs
(
)
print
(
result
)
|
上边呢,我取了一百页的数据,也就是大概2000条做测试。
成果验收
废了好大劲终于写完那些代码之后呢,就可以享受胜利的果实了,虽然是初学者,代码写的很渣,这参考一下,那参考一下,不过还是有些收获的。运行效果如下:
生成的文件:
文件内容:
总结
一个简单的抓取程序就写完了,python还真是TM的好用。以后有空再研究研究吧。代码行数算上空行和注释总共 100 (50+25+25) 行。凑个整数好看点~~现在认识字我感觉就可以上手写程序了。这里百度一下,那里google一下,问题就解决了,程序也出来了,大功告成。
是时候该和python暂时告别了,继续我的.NET事业。话说上次做rss采集的时候,好多“.NET要完蛋了”,“为什么我们不招.NET” 是什么鬼。 小伙伴们,下次见。