本文爬虫不涉及付费内容,所爬取数据都是公开的
1.request爬取静态加载数据
就拿爬取音乐歌单来举例吧,本人比较喜欢杨宗纬,就先爬取一下他的歌单吧。
首先是确定url,这个url就是你要爬的数据的地址。
url为https://music.163.com/artist?id=6066
url 确定之后就可以开始写爬虫了。
第一步,导入相关库
import requests
from lxml import html
from lxml import etree
import urllib
import re
第二步,写入url和请求头
写入headers,里面放入请求头是为了伪装成浏览器,如果不知道怎么来的可以自行百度,这里只教你怎么找到headers。
进入你想要爬取的url,按下F12打开开发者工具,点击Network,刷新一下页面,在Name里随便找一个点击,再找到Headers里面的Request Headers,里面的User-Agent参数就是我们要的headers。
url = 'https://music.163.com/artist?id=6066'
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
第三步,确定方法
现在已经有了url,headers,找到该页面的请求方法。
和上面一样,只不过最后是在这里。
很明显是GET方法,所以就用GET方法,编码用utf-8。
r = requests.get(url, headers=headers)
r.encoding = 'utf-8'
接下来就看看是否成功。
s = r.text
s
成功请求到了,接下来就是获得想要的数据了
第四步,获得数据
因为源码已经有了,现在唯一不确定的是我想要的歌单是否是动态加载。判断的方法很简单,第一个方法就是用xpath定位元素解析,查看数据是否存在。
首先你要学会xpath定位,如果不会的话可以到你想要的数据位置右键点击,到Copy,里面有选项为Copy Xpath,这种方法缺点就是只能定位一个元素,如果你想要爬取其他的就要自己写xpath定位。
我这里Xpath是//div[@class="ttc"]/span/a/b/@title
简单解释一下,/ 表示的是下一级标签,// 同理表示2级或多级,//div是我匹配div标签,但是div标签太多了,怎么能知道我要的是哪个呢,所以就用到了后面的class,因为我要的数据在b标签那里。
这时候写//div[@class="ttc"]//b/@title
是一样的,我上面是为让你们能好好理解 / 的作用。
好,理解了上面的Xpath之后就来获取数据吧
html = etree.HTML(s)
html.xpath('//div[@class="ttc"]/span/a/b/@title')
结果是有数据,但是不是我们想要的数据,应该是被加密了,反爬虫的手段。
还记得上面判断是否为动态加载的数据方法吗,第二种方法就是按下CTRL+F,查找你要的数据,看看所需数据是否在你请求到的网页源码里
看来是静态加载的,但是在Xpath定位获取数据的时候可能被加密了,才会出现上面的情况。
这里就需要用到正则了,正则太香了。如果你不会正则的话,可以去百度自行学习,并不难,但是掌握了之后匹配字符串,提取或者清洗数据很方便。
names = re.findall(r'<ul class="f-hide">(.*?)</ul>',s)
names
names = re.findall(r"<a href=(.*?)>(.*?)</a>",str(names))
names
因为爬取下来的数据终归是要保存下来的,所以我们可以用上面的数据做三列,歌曲名称,歌曲地址,歌曲id
song_url_list = []
song_names = []
for i in names:
split1 = i[0].split('"')[1]
song_url_list.append('https://music.163.com/#'+split1)
song_names.append(i[1])
song_url_list
song_id = [int(j) for i in names for j in re.findall(r'\"\/song\?id=(.*?)\"',i[0])]
song_id
获取歌手名字
t = re.findall(r'<textarea id="song-list-pre-data" style="display:none;">(.*?)</textarea>',s)
# t
#下面是正则匹配字符串次数,然后做相应次数的替换
#因为把他们变成json的时候报错,把false和null替换就好了
len(re.findall(r"false",str(t)))#250
t1 = str(t).replace("false","0",250)
len(re.findall(r"null",str(t)))#500
t2 = str(t1).replace("null","0",500)
import json
t3 = eval(t2[3:-3])
# t3
songer = []
for j in t3:
k = [i["name"]for i in j['artists']]
songer.append(k)
songer
很明显,这样的数据是不允许储存到表里的,太难看,清洗一下
songer_true = []
for i in songer:
k = re.sub(r"\[|\]",'',str(i))
k = re.sub(r"\'",'',k)
songer_true.append(k)
第五步,保存数据
album = [t3[i]['album']['name'] for i in range(50)]
import pandas as pd
df = pd.DataFrame()
df['演唱歌手'] = songer_true
df['歌曲名称'] = song_names
df['歌曲地址'] = song_url_list
df['歌曲专辑'] = album
df['歌曲id'] = song_id
df.to_csv('杨宗纬.csv',index=False)
2.Ajax爬取动态加载数据
第一步,找到数据所在url和相关参数
url的寻找方法在我写的2021电工杯数据建模B题的第三问的Ajax数据爬取
相关参数的位置同样在所找的url的Request Headers里,再加一个data,这个位置在Form Data里,值得注意的是,这里的请求方法变成了POST
import requests
Ajax_url = 'https://music.163.com/weapi/song/lyric?'
headers = {
'origin':'https://music.163.com',
'referer':'https://music.163.com/#/song?id=490602750',
'sec-ch-ua-mobile': '?0',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'X-Requested-With':'XMLHttpRequest',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36'
}
data={
'csrf_token':'a050496bc510ce2bdaab667e33bb77f1',
'params':'Z3bEY6b0ZvVu5EedFZHnGQaS0Vv/ZDJ4JOeV1ZvX3UIhk88p6BURTa2tPr08n+HRWDN3w9F4I5fX1sqbJVJQdk1m/MD9EBbB70RaiB1jJvtXiXIdG1YJtC7NFAt45Ar8EhtDvCcgIgAF13oN5QurLKQii4rZFXwcou6aiye5XaI9qZnD3KAF76M70Z6gVz73',
'encSecKey':'99cb31cca793ecb41e3d808dc8365e7ec92e9a5c20afb80e17b43e0e596b2a6a92480c93c515cf9da26d1b4ad53ce60a6ae8f5365435f6451a374caf50cfaa094f9d32ab04e24dd430dcbe0d32dd237d4e734a17a5ba8efe0536832c618732ad3d9f3cfb0d48d121ece0785bab6c8a78d3fc9a92f247df8edfb7b64595451226'
}
第二步,请求到数据,清洗,保存
请求
res = requests.post(url=Ajax_url,headers=headers,data=data)
res.text
清洗
import re
geci = re.findall(r'\](.*?)\\n',res.text)
保存
geci1 = pd.DataFrame()
geci1["越过山丘"] = geci
geci1.to_csv('《越过山丘》歌词.txt',index=False)
3.selenium爬取音乐评论
第一步,安装对应浏览器版本的驱动
首先需要你下载对应浏览器版本的驱动,我是谷歌浏览器,所以需要下载对应版本的chromedriver,查看浏览器版本在关于Chrome。
下载chromedriver的地址
一定要对应版本,不然没法用,下载好了把他放在Python目录下的Scripts文件里,还有放在C:\Program Files\Google\Chrome\Application
里,并配好环境变量。
第二步,爬取数据
值得一提的是,这里有一个内嵌页面。webDriver只能在一个页面上对元素识别和定位,对于frame/iframe表单内嵌页面上的元素无法直接定位,此时就需要通过switch_to.frame()方法将当前定位的主题切换为iframe表单的内嵌页面中。
selenium就是模拟用户的操作来进行爬取数据的,所以定位,点击等操作需要好好去看一下,如果需要登录的话,还可以提前将要输入的账号,密码写好,到时候直接放进去。这里不多介绍,有需要的可以去官方文档查看,学习一个库的最好方法就是去他的官方文档学习。不多介绍不是因为懒或者什么,是因为太笼统了,太抽象。
import pandas as pd
import re
f = open('杨宗纬.csv','r',encoding='utf-8')
df = pd.read_csv(f)
df.head()
from selenium import webdriver
import time
driver = webdriver.Chrome()
data= []#创建列表来储存数据
for url in df['歌曲地址']:
driver.get(url)
driver.switch_to.frame('g_iframe')#进入内嵌框架
time.sleep(5)#停5秒
for page in (range(3)):#这里是翻页的次数,每首歌爬取4页的评论
selectors = driver.find_elements_by_xpath('//div[@class="cmmts j-flag"]/div')#定位
try:
for selector in selectors:
text = selector.find_element_by_xpath('.//div[@class="cnt f-brk"]').text#评论者名字,评论内容等
num = selector.find_element_by_xpath('.//div[@class="rp"]').text#时间,点赞数等
data.append([url,text,num])
next_button = driver.find_element_by_xpath('//a[starts-with(@class,"zbtn znxt js-n-")]')#定位下一页按钮
driver.execute_script('arguments[0].click();', next_button)#点击
time.sleep(5)
except:
pass
driver.quit()#退出
第三步,数据清洗,并保存
p = [i[2] for i in data]#时间,点赞数
text_list = [i[1] for i in data]#评论
p1 = [re.findall(r"\((.*?)\)",i) for i in p]
p2 = [i for i in p1]
p3 = [re.findall(r"\'(.*?)\'",str(i)) for i in p2]
p4 = []
for i in p3:
try:
p4.append(int(i[0]))
except:
p4.append(0)
df1 = pd.DataFrame()
df1['回复时间'] = [i.split('\n')[0] for i in p]
df1['点赞数'] = p4
df1["用户id"] = [i.split(':')[0] for i in text_list]
df1['评论内容'] = [i.split(':')[1:] for i in text_list]
p2 = []
for i in range(len(df1['评论内容'])):
p2.append(df1['评论内容'][i][0])
df1['评论内容'] = p2
df1['歌曲地址'] = [(i[0]) for i in data]
song_id = []
for i in df1['歌曲地址']:
k = re.findall(r"\d",i)
k2 = ''
n = 3
while n<=len(k):
try:
k1 = k[n]
k2 +=k1
n +=1
except:
song_id.append(k2)
break
df1["歌曲id"] = song_id
df1.drop_duplicates(subset=['评论内容'],keep='first',inplace=True)
df1.to_csv('歌曲评论.csv',index=False,encoding='utf-8-sig')