要求:在B站搜索多个关键字,并将搜索到的视频结果(包括视频名,视频连接,点击量,收藏量,弹幕数和长传时间)录入数据库
了解网页
爬取数据前,我们需要先了解网页结构,以B站首页为例https://www.bilibili.com/
,按快捷键【Ctrl+U】打开源码页面
认识网页结构
网页一般由三部分组成,分别是 HTML(超文本标记语言)、CSS(层叠样式表)和 JScript(活动脚本语言)。
HTML
HTML 是整个网页的结构,相当于整个网站的框架。带“<”、“>”符号的都是属于 HTML 的标签,并且标签都是成对出现的。
标签 | |
---|---|
<head>..</head> | 包含了所有的头部标签元素。在 <head>元素中可以插入脚本(scripts), 样式文件(CSS),及各种 meta 信息。可以添加的元素标签为: <title>, <style>, <meta>, <link>, <script>, <noscript> 和 <base>。 |
<html>..</html> | 表示标记中间的元素是网页 |
<body>..</body> | 表示用户可见的内容 |
<div>..</div> | 表示框架 |
<p>..</p> | 表示段落 |
<li>..</li> | 表示列表 |
<img>..</img> | 表示图片 |
<h1>..</h1> | 表示标题 |
<a href="">..</a> | 表示超链接 |
CSS
CSS 表示样式,表示下面引用一个 CSS,在 CSS 中定义了外观。
JScript
JScript 表示功能。交互的内容和各种特效都在 JScript 中,JScript 描述了网站中的各种功能。
如果用人体来比喻,HTML 是人的骨架,并且定义了人的嘴巴、眼睛、耳朵等要长在哪里。CSS 是人的外观细节,如嘴巴长什么样子,眼睛是双眼皮还是单眼皮,是大眼睛还是小眼睛,皮肤是黑色的还是白色的等。JScript 表示人的技能,例如跳舞、唱歌或者演奏乐器等。
写一个简单的 HTML
打开一个记事本,输入下面的内容:
输入代码后,保存记事本,然后修改文件名和后缀名为"HTML.html",运行该文件后的效果,如图。<head>
<title> 这里是标题</title>
</head>
<body>
<div style="color:#0000FF">
<h1>这是被第一个div定义的标题1</h1>
<p>这里是被第一个div定义第一个段落</p>
</div>
<div style="color:#00FF00">
<ul>
<li><a href="https://www.baidu.com/">列表内容1 百度链接</a></li>
<li>列表内容2</li>
</ul>
</div>
</body>
使用 requests 库请求网站
若未安装 requests 库,可在终端中使用pip安装库:
pip install requests
爬虫的基本原理
网页请求的过程分为两个环节:
Request (请求):也就是向服务器发送访问请求。
Response(响应):服务器在接收到用户的请求后,会验证请求的有效性,然后向用户(客户端)发送响应的内容,客户端接收服务器响应的内容,将内容展示出来,就是我们所熟悉的网页请求
网页请求的方式也分为两种:
GET:最常见的方式,一般用于获取或者查询资源信息,也是大多数网站使用的方式,响应速度快。
POST:相比 GET 方式,多了以表单形式上传参数的功能,因此除查询信息外,还可以修改信息。
所以,在写爬虫前要先确定向谁发送请求,用什么方式发送。B站是使用get请求的,所以我们使用get方式抓取数据
获取B站视频搜索结果url列表
demand参数:表示搜索视频排序方式(watch:按播放量排序,dm:按弹幕量排序,time:按上传时间排序,collect:按收藏量排序)
kw参数:表示搜索的视频关键字列表
def bilibili_url (demand,kw):
if demand == 'watch':
url = 'https://search.bilibili.com/all?keyword={}&order=click&duration=0&tids_1=0'
elif demand == 'dm':
url = 'https://search.bilibili.com/all?keyword={}&order=dm&duration=0&tids_1=0'
elif demand == 'time':
url = 'https://search.bilibili.com/all?keyword={}&order=pubdate&duration=0&tids_1=0'
elif demand == 'collect':
url = 'https://search.bilibili.com/all?keyword={}&order=stow&duration=0&tids_1=0'
else :
url = 'https://search.bilibili.com/all?keyword={}'
url_list = []
for i in kw:
list_view = url.format(i)
url_list.append(list_view)
return url_list
信息爬取
def get_bilibili_play_info(url_list):
info = [] #建立一个空列表存放信息
seen = set() #建立一个空集合,去除重复字典
for j in url_list:
for i in range(50):
t=random.randint(0,9)
time.sleep(t) #随机sleep0到9秒防反爬
url=j + ('&page=%d' %(i+1)) #跳转页数,B站支持显示50页搜索信息
print (url)
headers = {'Accept': 'text/html, application/xhtml+xml, image/jxr, */*',
'Accept - Encoding':'gzip, deflate',
'Accept-Language':'zh-Hans-CN, zh-Hans; q=0.5',
'Connection':'Keep-Alive',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063'}
bz_data = requests.get(url,headers=headers)
#添加headers参数伪装浏览器反爬
soup = BeautifulSoup(bz_data.text,'lxml') #解析网页信息
for link in soup.find_all(name='li',class_="video-item matrix"):
#对每一个视频信息循环处理,link的信息如下图
info_dic = {}
for temp in link.find_all(name='a',class_="img-anchor"):
#提取a标签,temp信息如下图
info_dic['title'] = temp.get('title') #视频名信息
info_dic['url'] = "http:%str" %temp.get('href') #视频url信息
a = str(link.find('span',class_='so-icon watch-num'))
#a= '<span class="so-icon watch-num" title="观看"><i class="icon-playtime"></i>\n 1230.1万\n </span>'
if '万' in a:
watch_num=re.findall(r"\d+\.\d+", a)
watch_num[0]=(float(watch_num[0]))*10000
info_dic['watch_num']=int(watch_num[0])
else :
watch_num=re.findall(r"\d+", a)
watch_num[0]=float(watch_num[0])
info_dic['watch_num']=int(watch_num[0])
a = str(link.find('span',class_='so-icon hide'))
#'<span class="so-icon hide" title="弹幕"><i class="icon-subtitle"></i>\n 5.8万\n </span>'
if '万' in a:
hide=re.findall(r"\d+\.\d+", a)
hide[0]=(float(hide[0]))*10000
info_dic['dm']=int(hide[0])
else :
hide=re.findall(r"\d+", a)
hide[0]=int(hide[0])
info_dic['dm']=int(hide[0])
a = str(link.find('span',class_='so-icon time'))
#'<span class="so-icon time" title="上传时间"><i class="icon-date"></i>\n 2020-02-01\n </span>'
info_dic['upload_time']=re.findall(r"(\d{4}-\d{1,2}-\d{1,2})", a)[0]
#如果需要提取为datatime格式,可以用下面这条
#info_dic['上传时间']=datetime.date(*map(int, time_list[0].split('-')))
t = tuple(info_dic.items())
if t not in seen:
#筛选是否有重复的视频信息
seen.add(t)
info.append(info_dic)
return info
其中,每个视频的网页解析信息link结果如图:数据入库
参数:host,user,password,db为连接数据库的参数
def rq(host,user,password,db,table,info):
cols = ", ".join('`{}`'.format(k) for k in info[0].keys())
#提取列表中字典的键,格式为`key1`,`key2`.....
val_list = []
for i in info:
#仅保留列表中所有字典的值,以元组格式重新存入新列表
val_tup = tuple(i.values())
val_list.append(val_tup)
cols_val = '%s'
for j in range(len(list(info[0].keys()))-1):
#根据键个数添加%s
cols_val = cols_val + ",%s"
sql="insert into %s(%s) value(%s)"
res_sql = sql % (table,cols,cols_val)
#res_sql ="insert into table(`key1`,`key2`...) value(%s,%s...)"
db=pymysql.connect(host=host,user=user,password=password,db=db,charset='utf8')
#连接数据库
cursor = db.cursor()
cursor.executemany(res_sql,val_list)
#对数据库进行批量操作,其中val_list必须为以元组组成的列表格式
db.commit() # 提交更新的数据到数据库
cursor.fetchall() #查询处理后数据结果
print ('入库完成!')
# 关闭cursor游标
cursor.close()
#关闭服务器
db.close()