到目前为止我新做了如下事情:
其中对 MySql 的封装代码单独放到了文件下,做为一个 module 使用,这个 module 虽然简单,但已经实现了 select,insert,delete 等操作,对 MySql 封装感兴趣的同学可以参考, 但请不要用于生产环境。推荐去使用和阅读数据库类 peewee。
接下来将继续讲述我在数据抓取上的开发经历。
最终目标:收集到各大直播平台的主播信息和历史播放记录,进而对数据进行聚合分析。
当前已完成:对花椒网的数据收集。
沃米优选网 (http://video.51wom.com/) 是一个网红数据聚合的网站,它收集了各个直播平台 (花椒,熊猫,秒拍,斗鱼,映客,一直播,美拍) 的热门主播信息。所以我希望能从它这里获取到各个平台的热门主播信息,之后拿着主播 id 去对应的直播平台去爬取更详细的信息。
列表页 http://video.51wom.com / 截图如下:
初看这是一个列表页,并且底部有分页链接,点击分页时触发表单提交
当点击底部分页时,使用 chrom 开发者工具,看到有 XHR 请求如下截图:
从截图和一些测试可以分析出:
对于 cookie 容易拿到,但_csrf 如何获取呢?
查看页面源码,发现网站在生成列表页时已经将 csrf 的值写入了表单;同一个 csrf 值在后续请求中可以多次使用
- <input type="hidden" name="_csrf" value="aWF6ZGMzclc9EAwRK3Y4LhobNQo6eEAdWwA0IFd1ByUDNTgwClUEZw==">
由以上分析,程序的逻辑应该这样,
a) 构造基础类 class website, 之后为每个网站建立一个 class,继承 Website
注意以下代码为了节省篇幅,不是完整代码,也非 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, 封装对网站沃米优选的请求
- 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 代码,这是一个细致活;感受一下代码截图
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 是不一样的,需要你自己来做字符串和数字的类型转换
在沃米优选网拿到了各个直播平台的主播 id, 先实现对一下网 (http://www.yixia.com/) 的抓取,获取对应的主播和视频信息。
一下网的个人主页地址为 http://www.yixia.com/u/uid, 这个 uid 就是主播 id, 如下截图:
- 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.get_json(url, params=payload)
- msg = json_obj['msg']
- msg = BeautifulSoup(msg, 'html.parser')
- ### 解析视频标题
- titles = list()
- ps = msg.findAll('p')
- for p in ps:
- titles.append(p.get_text(strip=True)) # 视频标题
- ### 解析视频赞和评论数
- stats = list()
- divs = msg.findAll('div', {'class': 'list clearfix'})
- for div in divs:
- tmp = div.ol.get_text(strip=True)
- tmp = re.split('赞|\|评论', tmp)
- stats.append(tmp)
- ### 解析视频其他数据
- videos = list()
- divs = msg.findAll('div', {'class': 'D_video'})
- for (k, div) in enumerate(divs):
- video = dict()
- video['scid'] = div.attrs['data-scid']
- ### ------这里省略很多代码------
- return videos
- ### 骨架函数,获取每个视频的每个分页数据
- def spider_videos(self, suid, video_count):
- page = 1
- current = 0
- tbl_video = YiXiaVideo()
- while current < int(video_count):
- print('spider_videos: suid=' + suid + ', page=' + str(page))
- videos = self.get_video_list(suid, page)
- for video in videos:
- tbl_video.insert(video, replace=True)
- current += len(videos)
- page += 1
- return True
大部分还是 3.3 里的知识点,这里重点注意字符串和整形,浮点型数字的转换。比如粉丝数'2.3 万'是一个字符串,需要转成浮点数 2.3 或者整数 23000; 再比如'8,189'需要转成 8189.
以下截图为采集到的一下网视频数据:
这里列出我记录下来的参考链接:
来源: http://www.cnblogs.com/beatzeus/p/6169044.html