Python网络爬虫案例实战:静态网页爬取:Requests爬虫实践
至此,已经介绍了利用爬虫网络对静态网页进行爬取,下面直接通过两个实例来演示爬虫的实践。
3.12.1状态码521网页的爬取
1.问题发现
在做代理池的时候,发现了一种以前没有见过的反爬虫机制。在用常规的requests.get(url)方法对目标网页进行爬取时,其返回的状态码(status_code)为521,这是一种以前没有见过的状态码。再输出它的爬取内容(text),发现是一些JavaScript 代码,如图3-2所示。下面来探索一下。
2.分析原理
打开 Fiddler,爬取访问网站的包,如图3-3所示,发现浏览器对于同一网页连续访问了两次,第一次的访问状态码为521,第二次为200(正常访问)。看来网页加了反爬虫机制,需要两次访问才可返回正常网页。
下面来对比两次请求的区别。521的请求如图3-4 所示;200的请求如图3-5所示。
通过对比两次请求头,可发现第二次访问带了新的 Cookie值。再考虑上面程序对爬取结果的输出为JavaScript代码,可以考虑其操作过程为:第一次访问时服务器返回一段可动态生成 Cookie值的JavaScript代码;浏览器运行JavaScript 代码生成 Cookie值,并带Cookie重新进行访问;服务器被正常访问,返回页面信息,浏览器渲染加载。
3.执行流程
弄清楚浏览器的执行过程后,就可以模拟其行为通过Python 进行网页爬取。操作步骤如下:
- 用request.get(url)获取JavaScript 代码。
- 通过正则表达式对代码进行解析,获得JavaScript函数名,JavaScript函数参数和JavaScript函数主体,并将执行函数
eval()语句修改为return语句返回 Cookie值。 - 调用 execjs库的 executeJS()功能执行JavaScript代码获得 Cookie值。
- 将Cookie值转化为字典格式,用request.get(url,Cookie = Cookie)方法获取得到正确的网页信息。
4.实现代码
根据以上流程步骤,实现代码主要表现在:
(1)实现程序所需要用到的库。
import re #实现正则表达式
import execjs #执行JavaScript 代码
import requests #爬取网页
(2)第一次爬取获得包含JavaScript函数的页面信息后,通过正则表达式对代码进行解析,获得JavaScript函数名、JavaScript函数参数和JavaScript函数主体,并将执行函数eval()语句修改为return语句返回 Cookie值。
#js_html为获得的包含JavaScript函数的页面信息
#提取 JavaScript函数名
js_func_name = ''.join(re.findall(r'setTimeout\(\"(\D+ )\(\d+\)\"', js_htm1))
#提取 JavaScript函数参数
js_func_param = ''.join(re.findall(r'setTimeout\(\"(\D+ )\(\d+\)\"', js_htm1))
#提取 JavaScript函数主体
js_func = ''.join(re.findall(r'(function . *?)</script>',js_htm1))
(3)将执行函数 eval()语句修改为return语句返回 Cookie值。
#修改 JavaScript函数,返回 Cookie值
js_func = js_func.replace('eval("qo = eval;qo(po);")',' return po')
(4)调用 execjs库的executeJS()功能执行JavaScript代码获得 Cookie值。
#执行 JavaScript 代码的函数,参数为 JavaScript函数主体,JavaScript函数名和 JavaScript函数参数
def executeJS(js_func, js_func_name, js_func_param):
jscontext = execjs.compile(js_func) #调用 execjs.compile()加载JavaScript函数主体内容
func = jscontext.call(js_func_name, js_func_param) # 使用call()通过函数名和参数执行该函数
return func
cookie_str = executeJS(js_func, js_func_name,js_func_param)
(5)将Cookie值转化为字典格式。
#将Cookie值解析为字典格式,方便后面调用
def parseCookie(string):
string = string.replace("document.cookie = '", "")
clearance = string.split(';')[0]
return{
clearance.split('=')[0]:clearance.split('=')[1]}
cookie = parseCookie(cookie_str)
至此,在获得 Cookie后,采用带 Cookie的方式重新进行爬取,即可获得我们需要的网页信息。
3.12.2TOP250电影数据
本实践项目的目的是获取豆瓣电影TOP250的所有电影名称,网页地址为:https:
//movie.douban.com/top250。在此爬虫中,将请求头定制为实际浏览器的请求头。
1.网站分析
打开豆瓣电影TOP250的网站,右击网页的任意位置,在弹出的快捷菜单中单击“审查元素”命令即可打开该网页的请求头,如图3-6所示。
提取网站中重要的请求头代码为:
import requests
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Host': 'movie.douban.com'
}
第一页只有25部电影,如果要获取所有的250页电影,就需要获取总共10页的内容。通过单击第二页可以发现网页地址变成了:
https://movie.douban.com/top250?start = 25
第三页的地址为https://movie.douban.com/top250?start=50,这很容易理解,每多一页,就给网页地址的start参数加上25。
2.项目实践
通过以上分析,可以使用requests获取电影网页的代码,并利用for循环翻页。代码为:
# -*- coding: utf-8 -*-
import requests
def get_movies():
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Host': 'movie.douban.com'
}
for i in range(10): # 豆瓣电影 Top 250 分为 10 页
start = i * 25
link = f'https://movie.douban.com/top250?start={
start}'
try:
r = requests.get(link, headers=headers, timeout=10)
r.raise_for_status() # 检查请求是否成功
print(f"第 {
i+1} 页响应状态码:{
r.status_code}")
print(r.text) # 打印页面内容
except requests.RequestException as e:
print(f"请求第 {
i+1} 页失败:{
e}")
get_movies() # 调用函数
运行程序,输出如下:
第 9 页响应状态码:200
<!DOCTYPE html>
<html lang="zh-CN" class="ua-windows ua-webkit">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="renderer" content="webkit">
<meta name="referrer" content="always">
<meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" />
<title>
豆瓣电影 Top 250
</title>
<meta name="baidu-site-verification" content="cZdR4xxR7RxmM4zE" />
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="Sun, 6 Mar 2006 01:00:00 GMT">
这时,得到的结果只是网页的HTML 代码,还需要从中提取需要的电影名称。下面代码实现网页的内容解析:
import requests
from bs4 import BeautifulSoup
# 通过find定位标签
# BeautifulSoup文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
def bs_parse_movies(html):
movie_list = []
soup = BeautifulSoup(html, "html")
# 查找所有class属性为hd的div标签
div_list = soup.find_all('div', class_='hd')
# 获取每个div中的a中的span(第一个),并获取其文本
for each in div_list:
movie = each.a.span.text.strip()
movie_list.append(movie)
return movie_list
# css选择器定位标签
# 更多ccs选择器语法:http://www.w3school.com.cn/cssref/css_selectors.asp
# 注意:BeautifulSoup并不是每个语法都支持
def bs_css_parse_movies(html):
movie_list = []
soup = BeautifulSoup(html, "lxml")
# 查找所有class属性为hd的div标签下的a标签的第一个span标签
div_list = soup.select('div.hd > a > span:nth-of-type(1)')
# 获取每个span的文本
for each in div_list:
movie = each.text.strip()
movie_list.append(movie)
return movie_list
# XPATH定位标签
# 更多xpath语法:https://blog.csdn.net/gongbing798930123/article/details/78955597
def xpath_parse_movies(html):
et_html = etree.HTML(html)
# 查找所有class属性为hd的div标签下的a标签的第一个span标签
urls = et_html.xpath("//div[@class='hd']/a/span[1]")
movie_list = []
# 获取每个span的文本
for each in urls:
movie = each.text.strip()
movie_list.append(movie)
return movie_list
def get_movies():
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Host': 'movie.douban.com'
}
link = 'https://movie.douban.com/top250'
r = requests.get(link, headers=headers, timeout=10)
print("响应状态码:", r.status_code)
if 200 != r.status_code:
return None
# 三种定位元素的方式:
# 普通BeautifulSoup find
return bs_parse_movies(r.text)
# BeautifulSoup css select
return bs_css_parse_movies(r.text)
# xpath
return xpath_parse_movies(r.text)
movies = get_movies()
print(movies)
运行程序,输出如下:
响应状态码: 200
TOP250.py:7: GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.
The code that caused this warning is on line 7 of the file TOP250.py. To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.
soup = BeautifulSoup(html, "html")
['肖申克的救赎', '霸王别姬', '阿甘正传', '泰坦尼克号', '千与千寻', '这个杀手不太冷', '美丽人生', '星际穿越', '盗梦空间', '楚门的世界', '辛德勒的名单', '忠犬八公的故事', '海上钢琴师', '三傻大闹宝莱坞', '放牛班的春天', '机器人总动员', '疯狂动物城', '无间道', '控方证人', '大话西游之大圣娶亲', '熔炉', '教父', '触不可及', '当幸福来敲门', '寻梦环游记']