一、爬虫的矛与盾
1、反爬机制
门户网站,可以通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取。
2、反反爬策略
爬虫程序可以通过指定相关的策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站的信息。
3、robots.txt协议:
君子协议。规定了网站中哪些数据可以被爬虫爬取,哪些数据不可以被爬取。
4、http协议
概念:就是服务器和客户端进行数据交互的一种形式
5、常用请求头信息
(1)User-Agent:表示请求载体的身份标识
(2)Connection:请求完毕后,是断开连接还是保持连接
6、常用响应头信息
(1)Content-Type:服务器响应回客户端的数据类型
7、https协议:安全的超文本传输协议。(进行数据加密的,而http中不存在数据加密)
8、加密方式
(1)对称密钥加密
(2)非对称密钥加密
(3)证书密钥加密
二、requests第一次
requests模块:Python 中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高。
作用:模拟浏览器发请求
2.1、(requests模块的编码流程)如何使用
(1)指定url
(2)发起请求:使用get方法发起get请求,该方法返回一个响应对象。参数url表示请求对应的url
response = requests.get(url=url)
(3)获取响应数据:通过调用响应对象的text属性,返回响应对象中存储的字符串形式的响应数据(页面源码数据)
(4)持久化存储
with open('./sun.html','w',encoding = 'utf-8') as fp:
fp.write(page_text)
print('数据爬取完成')
2.2、环境安装
pip install requests
2.3、实战
# 爬取百度页面的页面数据
import requests
# step_1:指定url
url = 'https://www.baidu.com/'
# step_2:发起请求
response = requests.get(url=url)
# step_3;获取响应数据
page_text = response.text
print(page_text)
# step_4:持久化存储
with open('./baidu.html','w',encoding='utf-8') as fp:
fp.write(page_text)
print('over')
2.4、需求:爬取搜狗指定词条对应的搜索结果页面(简易网页采集器)
url的编码特性决定在百度中搜索的wd的关键字变为乱码。在requests中的乱码可以我们手动改为中文
2.4.1、处理url携带的参数:封装到字典中
2.4.2、UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器
UA检测: 门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求的载体身份标识为某一款浏览器,说明该请求是一个正确的请求。
但是检测到请求的身份标识不是基于某一款浏览器的,则表示该请求为不正常的请求。
则服务器端就很有可能拒绝该请求。
User-Agent(请求载体的身份标识)
UA伪装:将对应的User-Agent封装到一个字典中
headers = {
'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36 Edg/108.0.1462.46'
}
实战
# -*- coding: utf-8 -*-
# @Time : 2022/12/17 20:17
# @Author:未晞~
# @FileName: demo2.py
# @Software: PyCharm
# UA伪装
import requests
if __name__ == '__main__':
# UA伪装
header ={
'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36 Edg/108.0.1462.46'
}
url='https://www.baidu.com/s'
wd = input('enter a keyword:')
param = {
'query':wd
}
# 对指定的url发起的请求对应的url携带参数的,并且请求过程中处理了参数
response = requests.get(url=url,params = param,headers = header)
# step3获取响应数据
page_txt = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
fp.write(page_txt)
print(fileName,'保存成功!!!!!!!!!!!')
2.5、requests实战之破解百度翻译
(1)post请求(携带了参数)
(2)响应数据是一组json数据
根据response中返回的信息确定对应的数据包
# -*- coding: utf-8 -*-
# @Time : 2022/12/17 22:16
# @Author:未晞~
# @FileName: demo3.py
# @Software: PyCharm
import json
import requests
if __name__ == '__main__':
# 1、指定url
post_url = 'https://fanyi.baidu.com/sug'
# 2、UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36 Edg/108.0.1462.46'
}
# 3、post请求参数处理(同get请求一致)
/*data = {
'kw':'dog'
}*/
#改为动态的数据查询
word = input('enter a word:')
data = {
'kw':word
}
# 4、请求发送
response = requests.post(url=post_url,data=data,headers=headers)
# 5、获取响应数据:json()方法返回的是obj(如果确认响应数据是json类型的,才可以使用json())
dic_obj = response.json()
# 6、持久化存储
fileName = word+'.json'
#'./dog.json'替换为fileName
fp = open(fileName,'w',encoding='utf-8')
# 中文不能使用ascii码形式
json.dump(dic_obj,fp = fp,ensure_ascii=False)
print('over!!')
2.6、爬取豆瓣电影分类排行榜 https://movie.douban.com/中的电影详情数据
1、
2、代码
# -*- coding: utf-8 -*-
# @Time : 2022/12/17 23:09
# @Author:未晞~
# @FileName: demo4.py
# @Software: PyCharm
import json
import requests
if __name__ == '__main__':
url = 'https://movie.douban.com/j/chart/top_list'
param = {
'type': '24',
'interval_id':'100:90',
'action':'',
'start': '0',#从库中的第几部电影去取,索引从0开始
'limit': '20'#一次取出的个数
}
# 2、UA伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36 Edg/108.0.1462.46'
}
# 3、发起请求
response = requests.get(url=url,params = param,headers=headers)
# 4、获取响应对象
list_data = response.json()
fp = open('./douban.json','w',encoding='utf-8')
json.dump(list_data,fp,ensure_ascii=False)
print('over!!!!!!!')
运行之后的截图:
将生成的json文件进行在线的解析,如下图所示
2.7、需求:爬起肯德基餐厅查询中指定地点的餐厅数
点击查询之后,url没有变化,所以说明为Ajax请求
如下图所示,为POST请求
所含参数如下图:
2.7.1、自学分页的处理
首先我们需要考虑以下三个问题:
1)页面最大容量大于实际页面条目(该地区的餐厅少);
2)循环到了最后一个页面 ;
3)页面最大容量等于实际页面条目。
在单个页面中,我们要进行一个for循环解析json数据并将storeName逐个放进列表里面,这里大家可以自由发挥,用字典采集自己感兴趣的数据。单个页面又在大的while循环中,当判断Table1为空时,跳出循环,爬取结束。(没有学会)
response.status_code返回指示状态的数字(200正常,404未找到)
pageIndex表示要查询的页码,因此我们可以通过修改页码来爬取不同页数的数据;
pageSize:表示一页可以含有多少条数据。
最终版的代码
# -*- coding: utf-8 -*-
# @Time : 2022/12/18 18:31
# @Author:未晞~
# @FileName: kfctest2.py
# @Software: PyCharm
import requests
import json
def catchinfo(pageIndex):
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'
}
params = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': pageIndex,
'pageSize': '10'
}
response = requests.post(url,params,headers)
if response.status_code==200:
print('请求成功')
return json.loads(response.text)
if __name__ == '__main__':
pageIndex = 1
answer = 0
print('北京市所有的KFC店:')
while True:
json_data = catchinfo(pageIndex)
if not json_data['Table1']:
print('读取结束')
break
else:
with open(f'KFC.txt','w',encoding='utf-8') as fp:
fp.write(str(json_data))
for i in range(len(json_data['Table1'])):
print('【{}】{}'.format(json_data['Table1'][i]['storeName'],json_data['Table1'][i]['addressDetail']))
answer+=1
pageIndex+=1
print('共有',answer,'家店')
结果运行截图如下:
当然,也可以使用如下的方法进行爬取
# -*- coding: utf-8 -*-
# @Time : 2022/12/18 10:09
# @Author:未晞~
# @FileName: kfctest.py
# @Software: PyCharm
import json
import requests
def create_response(page):
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'
}
param = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': page,
'pageSize': '10'
}
# 不用json格式的数组录入,是因为追加的话,整体就不是json格式的数据了,如果解析json再拼接的话,更加耗费时间
# json.dump(json_data, fp=fp, ensure_ascii=False) 所以该方法被舍弃
response = requests.post(url, param, headers)
# json_obj = json.loads(response.text)
# fp = open('./kfc.json', 'w', encoding='utf-8')
# json.dump(json_obj, fp, ensure_ascii=False)
content = response.text
return content
if __name__ == '__main__':
for page in range(1,11):
content = create_response(page)
print(content)
print('overr!!!')
问题:Python POST请求返回值为-1000
# -*- coding: utf-8 -*-
# @Time : 2022/12/18 10:09
# @Author:未晞~
# @FileName: kfctest.py
# @Software: PyCharm
import json
import requests
def create_response(page):
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'
}
param = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': page,
'pageSize': '10'
}
response = requests.post(url, param, headers)
# json_obj = json.loads(response.text)
# fp = open('./kfc.json', 'w', encoding='utf-8')
# json.dump(json_obj, fp, ensure_ascii=False)
content = response.text
return content
if __name__ == '__main__':
for page in range(1,11):
content = create_response(page)
print(content)
原因一:
在复制url时,将?后面的内容删除了,会返回-1000
原因二:
param或data参数缺少
原因三:
动态参数被改为静态参数
params = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': page,
'pageSize': '10'
}
params = {
'cname': '',
'pid': '',
'keyword': '北京',
'pageIndex': 'page',
'pageSize': '10'
}
2.8、爬取国家药品监督管理局中基于中华人民共和国化妆品生产许可证相关数据
url = ’http://125.35.6.84:81/xk/’
2、分析Ajax请求
首先确认是否请求当前URL会获取首页信息,如获取首页信息,会获得相应企业名称的超链接,获取超链接则可以获取相应详情信息。
可通过对当前URL发送请求进行验证。
分析两个详情页的URL
http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=ff83aff95c5541cdab5ca6e847514f88
http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?id=827d2943f8734d5c848336066c97d2b6
发现URL:http://125.35.6.84:81/xk/itownet/portal/dzpz.jsp?域名相同
参数id不同,并且ID为对应企业的ID,并且发现与上述json串中ID相同
由此得出,ID值可以从首页对应的Ajax请求的json串中得出
域名和ID值可以拼接处一个完整的对应企业的详情数据的URL.
确认此为详情页的详情数据,刚才获取了这个Ajax请求的URL,可以对其发送请求。
分析两个别的详情页的Ajax请求的URL
http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById
发现,所有post请求的URL 都一样,ID不一样
如果我们可以批量获取多家企业的ID后,可以使其拼接形成对应企业详情数据Ajax请求的URL。
# -*- coding: utf-8 -*-
# @Time : 2022/12/18 19:28
# @Author:未晞~
# @FileName: costest1.py
# @Software: PyCharm
import requests
import json
#UA伪装(一次即可)
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400'
}
#指定URL
#批量获取不同企业的ID值(ctrl+r正则匹配工具栏)
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
id_list = []#存储企业的ID
all_detal_json = []#存储所有企业的详情数据
#分页处理(获取前6页)
for page in range(1,6):
page = str(page)
data = {#数据处理
'on':'true',
'page':page,#页码
'pageSize':'15',#数据量
'productName':'',
'conditionType':'1',
'applyname':'',
'applysn':'',
}
#发起请求和获取响应数据
json_ids = requests.post(url=url,data=data,headers=headers).json()
for dic in json_ids['list']:
id_list.append(dic['ID'])
# print(id_list)
#获取企业详情数据
#指定URL
post_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
#处理参数
for id in id_list:
data = {
'id':id
}
#发起请求,获取响应数据
detal_json = requests.post(url=post_url,data=data).json()
# print(detal_json,"------------ending-------------")
all_detal_json.append(detal_json)
#持久化存储all_detal_json
fp = open('./alldetal,json','w',encoding='utf-8')
json.dump(all_detal_json,fp=fp,ensure_ascii=False)
print("Over!")
三、数据解析
1、聚焦爬虫:爬取页面中指定的页面内容
(1)指定url
(2)发起请求
(3)获取响应数据
(4)数据解析
(5)持久化存储
2、数据解析分类
(1)正则
(2)bs4
(3)xpath
3、数据解析原理概述
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
(1)进行指定标签的定位
(2)标签或者标签对应的属性中存储的数据值进行提取(解析)
3.1、正则
3.1.1、爬取糗图百科
import requests
import re
import os
'''爬取糗事百科所有图片(利用re表达式)'''
# 如果目录不存在,则创建目录
if not os.path.exists('./pics'):
os.makedirs('./pics')
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
}
# 循环访问页数
for i in range(1,11):
url="https://www.qiushibaike.com/imgrank/page/%d/"%(i)
# text字符串 content 二进制数据 json对象
jpg_response=requests.get(url=jpg_url,headers=headers).content
# 二进制方式写入数据
with open('./pics/'+jpg_name,'wb') as fp:
fp.write(jpg_response)
print(jpg_name,"success")