1. 利用API爬取数据
有时,使用BeautifulSoup解析和提取数据后,输出结果为空。这是由于网页使用了另一种加载数据的方式——通过API加载数据。API(Application Programming Interface)即应用程序编程接口,也就是网页和服务器交互的途径。
1) Network
开发者工具中的Network记录的是从打开工具到网页加载完毕之间的所有请求。其中,以All开头的一行是请求类型过滤器,可以展示对应类型的请求。爬虫中常用的有All、XHR、Img和Media。
2) XHR
XHR全称XMLHttpRequest,是浏览器内置的对象,使得JavaScript可以发送HTTP请求。
爬取需要的数据时,首先要在XHR的Name栏中找到数据所在的位置(可通过请求名称和数据大小判断),找到目标后点击这条请求,在Preview中查看数据,即可确认。数据有很多,需要按名称逐层展开。
接下来,点击Headers里的General就可以看到API的链接(即Request URL),这就是获取数据的真正链接。但这个链接一般又长又乱,这时可以用requests.get()方法的params参数,以字典的形式传递链接的查询字符串参数,使代码看上去更加简洁明了。利用Convert HTTP Query String to JSON网站,将网址问号后的参数粘贴到input框中,即可得到转换后的字典形式的参数。
例如,爬取QQ音乐《等你下课》的评论,可以将代码写为:
import requests
headers = {
'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
params = {
'g_tk': '5381',
'loginUin': '0',
'hostUin': '0',
'format': 'json',
'inCharset': 'utf8',
'outCharset': 'GB2312',
'notice': '0',
'platform': 'yqq.json',
'needNewCode': '0',
'cid': '205360772',
'reqtype': '2',
'biztype': '1',
'topid': '212877900',
'cmd': '8',
'needmusiccrit': '0',
'pagenum': '0',
'pagesize': '25',
'lasthotcommentid': '',
'domain': '',
'ct': '24',
'cv': '10101010'
}
res = requests.get('https://c./base/fcgi-bin/fcg_global_comment_h5.fcg', headers=headers, params=params)
print(res.text)
用res.text获取到的数据是字符串形式的,而如果是字典或列表的话,会更容易从中提取评论数据。可以用JSON将其转换为字典或列表。
3) JSON
JSON(JavaScript Object Notation)是编程语言之间通用的数据格式。它的本质是一个字符串,建构于两种结构:键值对的集合和值的有序列表,分别对应Python里的字典和列表。它是一种标准,规定了基本数据结构的写法,不同的编程语言拿到后可以解析成自己对应的数据结构。
4) 解析JSON
对于JSON格式的内容,调用json()方法即可对响应内容解码。例如,获取最新的第一条评论内容,可以在上面的代码后续写:
data = res.json()
print(data['comment']['commentlist'][0]['rootcommentcontent'])
5) 获取多页数据
通过对比不同页码的链接参数可以得出其变化规律。
查看第二页评论的请求,可以发现只有pagenum和lasthotcommentid两个参数发生了变化,pagenum从0变成了1,而lasthotcommentid则是上页最后一条评论的commentid。因此,通过修改参数pagenum和lasthotcommentid的值即可获取多页的评论数据。
例如,获取《等你下课》前五页的评论,完整的代码可以写为:
import requests
import time
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
lasthotcommentid = ''
for pagenum in range(5):
params = {
'g_tk': '5381',
'loginUin': '0',
'hostUin': '0',
'format': 'json',
'inCharset': 'utf8',
'outCharset': 'GB2312',
'notice': '0',
'platform': 'yqq.json',
'needNewCode': '0',
'cid': '205360772',
'reqtype': '2',
'biztype': '1',
'topid': '212877900',
'cmd': '8',
'needmusiccrit': '0',
'pagenum': pagenum,
'pagesize': '25',
'lasthotcommentid': lasthotcommentid,
'domain': '',
'ct': '24',
'cv': '10101010'
}
res = requests.get('https://c./base/fcgi-bin/fcg_global_comment_h5.fcg', headers=headers, params=params)
data = res.json()
for item in data['comment']['commentlist']:
print('{}:{}'.format(item['nick'], item['rootcommentcontent']))
lasthotcommentid = data['comment']['commentlist'][-1]['commentid']
time.sleep(1)
总之,根据网站是静态的还是动态的,爬虫的策略有所不同。静态网页使用BeautifulSoup解析网页源代码,动态网页直接找到加载数据的API,从API中爬取数据。
2. cookie
1) 登录账号
在开发者工具中,点击Network,清空请求记录后勾选Preserve log,接下来在网页中输入账号密码登录,即可看到login或signin之类的请求。在这一条请求中,请求方式(Request Method)显示为POST。
2) POST请求
与GET一样,POST也是请求方式的一种。GET用于获取数据,其参数显示在请求地址里;POST用于提交数据,其参数隐藏在Form Data里。可以通过requests.post()来发送POST请求,其参数通过字典的形式传递给data参数。因此,登录账号的操作可以写为:
import requests
headers = {
'user-agent': '……'
}
data = {
'…': '…',
'…': '…'
}
requests.post('登录页面网址', data = data, headers = headers)
在发出POST请求后,服务器会在Response Headers(响应头)中返回一些关于该请求的信息,其中有关登录的信息是set-cookie,其作用是在浏览器中写入cookie,之后的请求中会带上cookie信息。
3) cookie
cookie是浏览器储存在用户电脑上的一小段文本文件,该文件中存了加密后的用户信息、过期时间等,且每次请求都会带上cookie。
4) 发评论
登录成功后,可以用Python做一些只有登录后才能进行的操作。例如,找到一篇文章的评论区,在开发者工具中将请求列表清空,接下来在网页中进行评论操作,即可在请求列表中找到发评论的请求。请求地址在General中,其他参数在Form Data中。
Note:Request请求之间的cookie不共享,需要手动将登录过后的cookie传给发评论的请求。
import requests
headers = {
'user-agent': '……'
}
# 登录参数
login_data = {
'…': '…',
'…': '…'
}
# 评论参数(其中的评论内容可自定义)
comment_data = {
'…': '…',
'…': '…'
}
# 发请求登录
login_req = requests.post('登录地址', data = login_data, headers = headers)
# 将登录后的cookies传递给cookies参数用于发评论请求
comment_req = requests.post('发评论请求地址', cookies = login_req.cookies, data = comment_data, headers = headers)
# 状态码为200表示评论成功
print(comment_req.status_code)
5) session
由于cookie容量有限,难以存储所有的用户信息,因此可以用session建立一份用户档案。cookie中只要存储用户的身份信息,服务器可以通过身份信息来在session中查询用户的其他信息。
可以通过requests.Session()创建一个session,多个请求之间就可以共享cookie了,后续请求便不需要再传cookie参数。此外,也可以通过session.headers.update()方法来更新全局的headers。因此,之前发评论的代码可以改写为:
import requests
headers = {
'user-agent': '……'
}
# 登录参数
login_data = {
'…': '…',
'…': '…'
}
# 评论参数(其中的评论内容可自定义)
comment_data = {
'…': '…',
'…': '…'
}
session = requests.Session()
session.headers.update(headers)
# 使用session登录
login_req = session.post('登录地址', data = login_data)
# 使用session发评论
comment_req = session.post('发评论请求地址', data = comment_data)
# 状态码为200表示评论成功
print(comment_req.status_code)
3. selenium
1) selenium
selenium是浏览器自动化测试框架,可以控制浏览器,模拟人浏览网页,从而获取数据、自动操作等。
可以使用pip install selenium来安装selenium,此外,还需安装相应的浏览器驱动。以Chrome浏览器为例,可打开/mirrors/chromedriver网站,找到对应的浏览器驱动,并下载zip文件。下载完成后将解压的exe放到Python安装目录的Scripts文件夹中。
2) 打开浏览器
用selenium打开Chrome浏览器的代码如下:
from selenium import webdriver
browser = webdriver.Chrome()
运行代码即可打开浏览器,然后将实例化后的浏览器对象赋值给browser变量,就可以操作该对象来控制浏览器。
3) 获取数据
将要打开的网址传给browser对象的get()方法,就可以打开对应的网页,调用quit()可将浏览器关闭。
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('')
browser.quit()
用selenium获取的网页源代码是数据加载完毕后最终的源代码,网页加载后通过API获取的数据也在其中,因此不必区分要爬取的网页是静态网页还是动态网页。
用print(browser.page_source)可以打印出网页源代码。
4) 处理数据
以获取百度首页的div标签为例:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('')
div = browser.find_element_by_tag_name('div')
print(div.text)
browser.quit()
用selenium查找元素的方法如下:find_element_by_tag_name:通过标签查找元素
find_element_by_class_name:通过class属性名查找元素
find_element_by_id:通过id查找元素
find_element_by_name:通过name属性查找元素
find_element_by_link_text:通过链接文本查找元素
find_element_by_partial_link_text:通过部分链接文本查找元素
通过这些方法得到的返回值都是WebElement对象,它与Tag对象相似,可以获取文本内容和属性值:WebElement.text:获取元素文本内容
WebElement.get_attribute('属性名'):获取元素属性值
上面的方法获取的是第一个符合条件的元素,如果要查找所有符合条件的元素,将方法中的element改为elements即可。
此外,也可以将selenium与BeautifulSoup结合起来,共同完成获取数据和处理数据的工作。例如,获取网页源代码中所有a标签:
soup = BeautifulSoup(browser.page_source, 'html.parser')
a_tags = soup.find_all('a')
for tag in a_tags:
print(tag.text)
browser.quit()
5) 控制浏览器click():点击元素
send_keys():模拟按键输入
通过以上两个方法,在查找到对应的元素后,调用click()方法可以模拟点击该元素,一般用于点击链接或按钮;调用send_keys()方法模拟按键输入,传入要输入的内容即可,一般用于账号密码等输入框的表单填写。
例如,在百度中搜索python并打印搜索结果的标题:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('')
# 找到搜索框并输入python
search = browser.find_element_by_id('kw')
search.send_keys('python')
# 点击搜索按钮
submit = browser.find_element_by_id('su')
submit.click()
# 找到搜索结果标题
titles = browser.find_element_by_class_name('qnBI_e')
# 打印搜索结果标题
for title in titles:
print(title.text)
browser.quit()
为了提升爬取效率,也可以通过以下代码将浏览器设置为静默模式,让浏览器在后台获取数据、操作页面:
options = webdriver.ChromeOptions()
options.add_argument('--headless')
browser = webdriver.Chrome(options = options)
Reference:
[1] Wes McKinney. . Python for Data Analysis: Data Wrangling with Pandas, NumPy, and IPython, 2nd Edition [M]. O'Reilly Media, Inc.
[2] CSDN博客. Python博客[OL]. /nav/python. .