(点击
上方公众号
,可快速关注)
来源:伯乐在线专栏作者 - octans
欢迎投稿,请点击这里查看详情;
如需转载,发送「转载」二字查看说明
本篇是文章
Python初学者之网络爬虫
的后续,最新代码已提交到https://github.com/octans/PythonPractice
1. 上篇回顾
上篇文章Python初学者之网络爬虫中,我从花椒的热门推荐页面入手,进而获取到主播个人信息和对应的直播历史视频。
首先看一下上一篇文章中对huajiao.com的主播和视频的爬取成果:
# getUserCount
10179
# getLiveCount
111574
到目前已收集了10179个主播信息,和这些主播的111574个视频信息。这里数据量小的原因是我只收集了花椒热门推荐下面的主播,这个页面每次展示60个系统推荐的主播。
到目前为止我新做了如下事情:
其中对MySql的封装代码单独放到了文件mysql.py下,做为一个module使用,这个module虽然简单,但已经实现了select,insert,delete等操作,对MySql封装感兴趣的同学可以参考, 但请不要用于生产环境。推荐去使用和阅读数据库类peewee。
接下来将继续讲述我在数据抓取上的开发经历。
2. 爬取的数据源和逻辑
最终目标:收集到各大直播平台的主播信息和历史播放记录,进而对数据进行聚合分析。
当前已完成:对花椒网的数据收集。
沃米优选网(http://video.51wom.com/)是一个网红数据聚合的网站,它收集了各个直播平台(花椒,熊猫,秒拍,斗鱼,映客,一直播,美拍)的热门主播信息。所以我希望能从它这里获取到各个平台的热门主播信息,之后拿着主播id去对应的直播平台去爬取更详细的信息。
3. 爬取沃米优选网的主播列表页
列表页http://video.51wom.com/截图如下:
初看这是一个列表页,并且底部有分页链接,点击分页时触发表单提交
3.1 分析结论和构思程序逻辑
当点击底部分页时,使用chrom开发者工具,看到有XHR请求如下截图:
从截图和一些测试可以分析出:
-
a) 要请求第二页以后的数据,需要将相应的cookie和csrf数据提交给网站;
-
b) 提交的方式是POST的”multipart/form-data”;
-
c) 提交的参数有_csrf, stage-name, platform, industry等;
-
d) 请求的返回结果是一个表格列表的html代码;
对于cookie容易拿到,但_csrf如何获取呢?
查看页面源码,发现网站在生成列表页时已经将csrf的值写入了表单;同一个csrf值在后续请求中可以多次使用;
由以上分析,程序的逻辑应该这样,
-
a) 先请求主播列表的首页,获取到csrf值和cookie
-
b) 将csrf和cookie值保存,用于下次请求
-
c) 请求主播列表的第二页,第三页等
-
d) 将获取到的表格列表的html代码使用BeautifulSoup进行解析,遍历每个行,行里的每个列
-
e) 将获取到的数据写入mysql
3.2 python编码获取沃米优选网的主播信息
a) 构造基础类class Website, 之后为每个网站建立一个class,继承Website
-
有些请求返回的是html代码,类里设置好html解析器;
-
有些请求返回的是json串,基类里设置好json的解析器;
-
请求每个网站时,需要设置不同的header,将header放在基类;
-
对post的Content-Type:multipart/form-data方式进行函数封装;
-
对post的Content-Type: application/x-www-form-urlencoded方式分别进行函数封装;
-
这里面尽量把各种不同的请求方式写成函数,而不使用type参数的形式,便于子类清晰的调用;
注意以下代码为了节省篇幅,不是完整代码,也非PEP8代码规范
class
Website
:
### 使用requests.session()能够自动处理cookies
session
=
requests
.
session
()
### 设置html解析器
htmlParser
=
BeautifulSoup
### 设置json解析器
jsonParser
=
json
### 设置headers
headers
=
{
'user-agent'
:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/'
'54.0.2840.98 Safari/537.36'
}
### 直接发起get请求
def
get
(
self
,
url
,
params
=
None
)
:
if
params
is
None
:
params
=
{}
return
self
.
session
.
get
(
url
,
params
=
params
,
headers
=
self
.
headers
)
### 发起get请求并返回解析后的html对象
def
get_html
(
self
,
url
,
params
=
None
)
:
r
=
self
.
get
(
url
,
params
)
return
self
.
htmlParser
(
r
.
text
,
'html.parser'
)
### 发起get请求并返回解析后的json对象
def
get_json
(
self
,
url
,
params
=
None
)
:
r
=
self
.
get
(
url
,
params
)
return
self
.
jsonParser
.
loads
(
r
.
text
)
### 发起post请求,以Content-Type:multipart/form-data方式
def
post_multi_part
(
self
,
url
,
params
)
:
kwargs
=
dict
()
for
(
k
,
v
)
in
params
.
items
()
:
kwargs
.
setdefault
(
k
,
(
None
,
v
))
r
=
self
.
session
.
post
(
url
,
files
=
kwargs
,
headers
=
self
.
headers
)
return
self
.
htmlParser
(
r
.
text
,
"html.parser"
)
b) 构造class WoMiYouXuan, 封装对网站沃米优选的请求
-
方法first_kiss()用于第一次请求网站,获取到csrf值由属性self.csrf保存;
-
first_kiss()另一个作用是获取到cookie,虽然没有显示处理,因为requests.session()帮我们处理了,自动获取自动提交;
-
注意在一个实例里,只需调用一次first_kiss()即可,之后就可以多次调用其他的页面请求函数了;
-
csrf和cookie是由关联的,网站会校验,都要提交;
-
方法parse_actor_list_page()是具体分析主播的列表html代码,这是一个细致活;
-
方法spider_actors是骨架函数,循环访问每个分页数据并将结果写入mysql;
class
WoMiYouXuan
(
Website
)
:
### 发起post请求时需要将csrf发给网站
csrf
=
''
def
__init__
(
self
)
:
self
.
first_kiss
()
### 首次访问该网站获取到csrf值并保存到self.csrf, 供其他post请求直接使用
def
first_kiss
(
self
)
:
url
=
'http://video.51wom.com/'
html
=
self
.
get_html
(
url
)
self
.
csrf
=
html
.
find
(
'meta'
,
{
'name'
:
'csrf-token'
}).
attrs
[
'content'
]
### 从主播列表页获取主播信息
def
parse_actor_list_page
(
self
,
page
=
1
)
:
### 构造参数->发起post请求
url
=
'http://video.51wom.com/media/'
+
str
(
page
)
+
'.html'
keys
=
(
'_csrf'
,
'stage-name'
,
'platform'
,
' industry'
,
'price'
,
'follower_num'
,
'follower_area'
,
'page'
,
'is_video_platform'
,
'sort_by_price'
,
'type_by_price'
)
params
=
dict
()
for
key
in
keys
:
params
.
setdefault
(
key
,
''
)
params
[
'_csrf'
]
=
self
.
csrf
params
[
'page'
]
=
str
(
page
)
html
=
self
.
post_multi_part
(
url
,
params
)
### 解析主播列表
trs
=
html
.
find
(
'div'
,
{
'id'
:
'table-list'
}).
table
.
findAll
(
'tr'
)
trs
.
pop
(
0
)
# 去除标题行
actor_list
=
list
()
for
tr
in
trs
:
### 后面太多了,有兴趣的同学去看源码吧
### 骨架函数,循环访问每个分页数据并将结果写入mysql
def
spider_actors
(
self
)
:
page
=
1
tbl_actor
=
WMYXActor
()
while
True
:
ret
=
self
.
parse_actor_list_page
(
page
)
for
actor
in
ret
[
'items'
]
:
actor
[
'price_dict'
]
=
json
.
dumps
(
actor
[
'price_dict'
])
tbl_actor
.
insert
(
actor
,
replace
=
True
)
if
ret
[
'items_count'
]
*
ret
[
'page'
]
ret
[
'total'
]
:
page
+=
1
else
:
break
方法parse_actor_list_page()具体分析主播列表的html代码,这是一个细致活;感受一下代码截图
3.3 知识点总结
a) 表单提交的POST方式
通常只提交一些kv数据时,使用application/x-www-form-urlencoded方式;
通常上传文件时,使用multipart/form-data方式,但此种方式也是可以提交kv类数据的,比如上面的获取主播列表数据时就是使用此方式。
b) Python的网络请求库Requests
这个库太好用了!并且能够对cookie自动处理,比如我在基类Website中的使用方式; 并且使用它构造multipart/form-data方式的post请求也很方便,比如方法Website::post_multi_part()
c) Python中使用正则匹配字符串中的整数,如下代码:
avg_watched
=
tds
[
6
].
get_text
(
strip
=
True
)
# 平均观看人数
mode
=
re
.
compile
(
r
'\d+'
)
tmp
=
mode
.
findall
(
avg_watched
)
d) 使用try, except机制来实现类似php里的isset(),如下代码:
# 判断是否有逗号,比如8,189
try
:
index
=
string
.
index
(
','
)
string
=
string
.
replace
(
','
,
''
)
except
ValueError
:
string
=
string
e) 一定要注意python中的’1’和1是不一样的,需要你自己来做字符串和数字的类型转换
4. 爬取秒拍网的主播和视频信息
在沃米优选网拿到了各个直播平台的主播id, 先实现对一下网(http://www.yixia.com/)的抓取,获取对应的主播和视频信息。
一下网的个人主页地址为http://www.yixia.com/u/uid, 这个uid就是主播id, 如下截图:
4.1 分析结论和构思程序逻辑
-
a) 在主播个人主页能够拿到主播的个人信息,如头像,昵称,粉丝数等,还能拿到主播的视频列表;
-
b) 视频列表的加载方式是瀑布流方式,意味着走的是ajax接口;
-
c) 视频列表接口返回的数据是html代码,仍然需要用BeautifulSoup解析;
-
d) 请求视频列表接口时需要提交suid参数,这个参数值需要用uid在主播个人页获取;
4.2 python编码一下网的主播信息和视频列表
class
YiXia
(
Website
)
:
### 访问主播页面,也是视频列表页,从该页面获取到suid和主播个人信息
def
parse_user_page
(
self
,
uid
)
:
print
(
self
.
__class__
.
__name__
+
':parse_user_page, uid='
+
uid
)
user
=
dict
()
user
[
'uid'
]
=
uid
url
=
'http://www.yixia.com/u/'
+
uid
bs
=
self
.
get_html
(
url
)
div
=
bs
.
find
(
'div'
,
{
'class'
:
'box1'
})
user
[
'nickname'
]
=
div
.
h1
.
a
.
get_text
(
strip
=
True
)
# 昵称
stat
=
div
.
ol
.
get_text
(
strip
=
True
)
stat
=
re
.
split
(
'关注\||粉丝'
,
stat
)
user
[
'follow'
]
=
stat
[
0
].
strip
()
# 关注数
user
[
'followed'
]
=
stat
[
1
].
strip
()
# 粉丝数
### ------这里省略很多代码----
return
user
### AJAX请求视频列表
def
get_video_list
(
self
,
suid
,
page
=
1
)
:
url
=
'http://www.yixia.com/gu/u'
payload
=
{
'page'
:
page
,
'suid'
:
suid
,
'fen_type'
:
'channel'
}
json_obj
=
self
.