概念:1.模拟客户端
2.发送网络请求,获取
3.按照规则自动提取数据的程序
分类:1.通用爬虫:搜索引擎(什么都抓,不挑食),百度,谷歌,必应
通用爬虫和聚焦爬虫工作原理:
1.搜索引擎原理
抓取网页
数据存储
预处理
提供检索服务,网站排名
2.聚焦爬虫原理
url list
响应内容 提取url
提取数据
入库
3.robots.txt 文件一般放置在网站根目录下
HTTP:超文本传输协议,默认端口号:80
HTTPS:HTTP+SSL(安全套接字层) 默认端口号:443
HTTPS和HTTP更安全,但是性能更低
url的格式:
浏览器地址栏的链接地址https://www.baidu.com/
形式 scheme://host[:port#]/path/.../[?query-string][#anchor]
scheme:协议(例如:http,https,ftp)
host:服务器的IP地址或者域名
port:服务器的端口(如果是走协议默认端口,80 or443)
path:访问资源的路径
http://www.cnblogs.com/be-saber/p/4734951.htm
浏览器会根据html标签,自动再次请求,获取图片,css,js等内容,叫做渲染
爬虫只会根据url地址,获取网页地址,发送请求,获取响应
重点:浏览器访问一个网站,在获取网站源码(HTML)之后,会根据源码上的链接加载图片,js ,css文件等,我们称之为渲染,而爬虫只会请求指定的url对应的网页源码,不会自动加载css,js,图片
对于爬虫,最重要的是User-agent,其次:cookie, referer ,accept等
# 操作系统的版本,浏览器内核,版本信息等
# 白氏客户端是谁?
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
# 表示从哪来?
# 也是CSRF防护手段之一
Referer: https://www.baidu.com/
总结:
爬虫代码中:必须要用的用户代理,其次,referer和cookie等请求头信息
HOST(主机和端口号)
Connection(链接类型)
Upgrade-Insecure-Requests(升级为HTTPS请求)
User-Agent(用户代码)
服务器能够识别客户使用的操作系统及版本,CPU类型,浏览器及版本,浏览器渲染引擎,浏览器语言,浏览器插件等
Accept(传输文件类型)
Referer
页面跳转处,从何处来到当前页面,可用于防爬和防盗链
Accept-Encoding
浏览器支持的编码类型,主要的编码格式就是压缩格式 gizp compress deflate
Cookie
用户进行状态保持,通常也可用于识别用户身份
x-requested-with:
XMLHttpRequest xhr是Ajax 异步请求
get方法
用来请求资源
在url中传输实体数据
传输的数据量小(受浏览器限制)
post方法
用来传输实体数据(本质上更专业)
在实体中传输数据
传输的数据量大
200:成功
客户端---请求正常处理则返回200 ok------服务器
爬虫代码中:不能百分百的信任服务器返回的状态码,必须以服务器返回的响应中是否有数据为准,为唯一标准。
如果浏览器中能看到数据,爬虫代码中获取不到,对比代码和浏览器的请求的区别。
如果遇到异常状态码,对比代码中的请求信息,和浏览器中的请求信息(请求头)和区别。
bytes:二进制
互联网上数据的都是以二进制的方式传输的
str:unicode的呈现形式
字符集(Character set)是多个字符的集合
字符集包括:ASCLL字符集,GB2312字符集,GB18030字符集,Unicode字符集等
ASCLL编码是1个字节,而Unicode编码通常是2个字节(UCS-2)
UTF-8是Unicode的实现方式之一,UTF-8是它是一种变长的编码方式,可以是1,2,3个字节
字符串数据使用encode方法转化为bytes
Bytes类型数据通过decode转化为字符串类型数据
编码方式是默认是utf-8,如果需要选定编码方式,编码方式解码方式必须一样,否则就会出现乱码
需求:通过requests向百度首页发送请求,获取百度首页的数据
response=requests.get(url)
response的常用属性
response.txt 获取str类型的响应
response.context 获取bytes类型的响应
response.status_code 获取状态码
response.headers 获取响应头
response.request 获取响应对应的请求
response.text
类型:str
解码类型:根据HTTPS头部对响应的编码作出有根据的推测,推测的文本编码
如何修改编码方式:response.encoding='gbk'
response.content
类型:bytes
解码类型:没有指定
如何修改编码方式:response.content.decode('utf8')
更推荐使用response.content.decode('utf8')的方式获取响应的html页面
headers的形式:字典
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/110.0.0.0 Safari/537.36'}
# 发送请求,伪装成浏览器
resp2 = requests.get(url, headers=headers)
with open('baidu4.html', 'w') as f:
f.write(resp2.content.decode('utf8'))
参数的形式:字典
kw={'wd':'python'}
用法:requests.get(url,params=kw)
# 单页爬取
"""
爬虫代码实现步骤:
# 1.要准备其实url地址和请求头信息
# 2.发送请求,获取响应
# 3.保存信息
# 4.定义入口函数
"""
import requests
class TuPian(object):
def __init__(self):
self.url = 'https://m.baidu.com/sf/vsearch?pd=image_content&word=%C3%C0%CD%BC&tn=vsearch&atn=page'
self.headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit'
'/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
def get_data(self):
resp = requests.get(self.url, self.headers)
return resp.content.decode('utf-8')
def save(self, data):
with open('tupian.html', 'w', encoding='utf-8') as f:
f.write(data)
def run(self):
data = self.get_data()
self.save(data)
if __name__ == '__main__':
tupian = TuPian()
tupian.run()
"""
爬虫代码实现步骤:
# 1.要准备其实url地址和请求头信息
# 2.发送请求,获取响应
# 3.保存信息
# 4.定义入口函数
"""
#------爬取吧的页数---------
import requests
class TieBa(object):
def __init__(self, pn):
self.url = 'https://tieba.baidu.com/f?kw=%E4%B8%8A%E6%B5%B7%E8%B4%B4&ie=utf-8&pn='
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
self.url_list = [self.url + str(i * 50) for i in range(pn)]
print(self.url_list)
def get_data(self, url):
# 发送请求,获取响应
resp = requests.get(url, self.headers)
return resp.content.decode('utf-8')
def save_data(self, data, index):
# 保存数据
file_name = 'tieba_' + str(index) + '.html'
with open(file_name, 'w', encoding='utf-8') as f:
f.write(data)
def run(self):
# 入口函数,在类的内部,实现各个功能函数之间的协调调用
# 遍历url列表,把没页的url传给发送请求的函数
for url in self.url_list:
data = self.get_data(url)
index = self.url_list.index(url)
print('index=',index)
self.save_data(data, index)
if __name__ == '__main__':
tieba = TieBa(3)
tieba.run()
#----爬取不同的吧,且不覆盖之前爬取的吧-------------
import requests
class TieBa(object):
def __init__(self, name,pn): # name 为吧名字
self.url = 'https://tieba.baidu.com/f?kw={}ie=utf-8&pn='.format(name)
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
self.url_list = [self.url + str(i * 50) for i in range(pn)]
self.name = name
print(self.url_list)
def get_data(self, url):
# 发送请求,获取响应
resp = requests.get(url, self.headers)
return resp.content.decode('utf-8')
def save_data(self, data, index):
# 保存数据
file_name = self.name + str(index) + '.html'
with open(file_name, 'w', encoding='utf-8') as f:
f.write(data)
def run(self):
# 入口函数,在类的内部,实现各个功能函数之间的协调调用
# 遍历url列表,把没页的url传给发送请求的函数
for url in self.url_list:
data = self.get_data(url)
index = self.url_list.index(url)
print('index=', index)
self.save_data(data, index)
if __name__ == '__main__':
tieba = TieBa('李毅', 3)
tieba.run()
#----------终端命令输入---------
import requests
class TieBa(object):
def __init__(self, name, pn): # name 为吧名字
self.url = 'https://tieba.baidu.com/f?kw={}ie=utf-8&pn='.format(name)
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)'
' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36'}
self.url_list = [self.url + str(i * 50) for i in range(pn)]
self.name = name
print(self.url_list)
def get_data(self, url):
# 发送请求,获取响应
resp = requests.get(url, self.headers)
return resp.content.decode('utf-8')
def save_data(self, data, index):
# 保存数据
file_name = self.name + str(index) + '.html'
with open(file_name, 'w', encoding='utf-8') as f:
f.write(data)
def run(self):
# 入口函数,在类的内部,实现各个功能函数之间的协调调用
# 遍历url列表,把没页的url传给发送请求的函数
for url in self.url_list:
data = self.get_data(url)
index = self.url_list.index(url)
print('index=', index)
self.save_data(data, index)
if __name__ == '__main__':
import sys # 获取程序外的输入
import codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
# print('argv[0]=', sys.argv[0]) # 传文件名
# print('argv[1]=', sys.argv[1]) # 传名字 李毅
# print('argv[2]=', sys.argv[2]) # 次数 3
name = sys.argv[1]
pn = int(sys.argv[2])
tieba = TieBa(name, pn)
tieba.run()
登录注册(POST比GET更安全)
绝大多数的登录会使用post请求,极少数网站仍然在使用get请求进行登录
向服务器传输的数据量比较多的时候,或者向服务器传输大文件
所以同样的,我们的爬虫也需要在这两个地方会去模拟浏览器发送post请求
cookie和session
二者区别:
cookie数据存放在客户的浏览器上,session数据放在服务器上
cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗(使用用户的cookie获取相关信息)。
session会在一定时间内保存在服务器。当访问增多,会比较占用服务的性能。
单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点保存的cookie的信息
1.requests模块中的Session,能够实现自动保存服务器返回的会话信息
方向代理:代理的服务器,隐藏了服务器
正向代理:代理的客户端,隐藏了客户端
import requests
"""
代理ip的使用,在工作中必须写爬虫代码,必须使用代理ip(不止一个ip),使用高匿代理
"""
url = 'http://www.baidu.com'
# 定义字典,保存代理字典,代理ip使用付费的,不会只用一个
proxy = {'http': ''}
# 不使用代理ip,发送请求
resp1 = requests.get(url)
print(resp1.status_code)
# 使用代理ip,发送请求,免费代理Ip不好用
resp2 = requests.get(url, proxies=proxy)
print(resp2.status_code)
cookiesjar与字典之间的转换
requests.utils.dict_from_cookiejar 把cookiejar对象转化为字典格式的cookies
requests.utils.cookiejar_from_dict 把字典格式的cookies转换成cookiejar对象
import requests
# cookiejar和字典之间的转换
# 作用:动态获取服务器返回cookie信息,代码中动态处理,不是在请求头中固定写死cookie信息
url = 'http://www.baidu.com'
resp = requests.get(url)
print(resp.cookies)
cookie_jar = resp.cookies
cookie_dict = requests.utils.dict_from_cookiejar(cookie_jar)
print(cookie_dict)
# 把字典转换为cookie对象
print(requests.utils.cookiejar_from_dict(cookie_dict))
请求SSL整数验证
使用场景:
Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。SSL验证默认是开启的,如果证书验证失败,Requests会抛出SSLError
使用方式:response=requests.get('https://12306.cn/mormhweb/'verify=False)
import requests
# 只有在访问https类型的网站才会遇到
url = 'https://sam.huat.edu.cn:8433/selfservice/'
# 默认开启CA证书认证
# resp = requests.get(url)
# 关闭认证
resp = requests.get(url, verify=False)
print(resp.status_code)
设置超时
使用场景:有些站点或者代理反应慢,严重降低效率,这时候可以设置超时
使用方式:response=requests.get(url,timeout=10)
import requests
url = 'http://www.baidu.com'
# 定义字典,保存代理ip,代理ip使用付费的,不会只用一个
proxy = {'http': 'http://123.182.58.46:8089'}
# 使用代理ip,发送请求,免费代理ip不好用
# timeout表示发送请求的超时时间,单位秒,有一定的网络延时,不是秒表
# timeout作用:可以用来测试代理IP是否好用
resp2 = requests.get(url, proxies=proxy, timeout=5)
print(resp2.status_code)
数据化结构:json,xml等
处理方法:转化为python数据类型
注意:xml:可扩展标记语言,标签可以自定义
html:标签不能自定义
# 标签都是预定好的,只能选择用或者不用
# 作用:主要用来展示数据
退出
# xml标签可以自定义,微信使用xml传输数据,包括:表情,文字,红包,视频,语言等数据:
# 作用:主要用来传输数据,传输账号和密码,python处理xml数据,pip install xmltodict模块,parse和unparse
zhangsan
123456
# 多数网站都是使用json较多,轻量级的数据交互格式
'{'name':'zhangsan','password':'123456'}'
把json格式字符串转换为Python字典类型很简单,所以爬虫中,如果我们能够找到返回json数据格式字符串的url,就会尽量使用这种url
dumps 把字典转json
loads 把json转成字典
数据包查找:
找数据包类型document,看第一个,根据网页的数据的关键字,搜索数据包是否有数据
如果第一个数据包没有,后面的数据包较多,直接找xhr
# 通过浏览器测试,网页中能够展示数据包的url地址
# https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A
# 实现步骤:
# 1.准备起始的url和请求头
# 2.发送请求,获取响应
# 3.解析响应,提取数据
# 4.保存数据
# 5.程序运行入口
# """
#
# class DouBan(object):
# # def __init__(self):
# # self.url = 'https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A'
# # self.headers = {
# # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
# # 'Chrome/110.0.0.0 Safari/537.36'}
# # # 打开文件
# # self.file = open('douban.json', 'w', encoding='utf-8')
# #
# # def get_data(self):
# # # 发送请求,获取响应
# # resp = requests.get(self.url, headers=self.headers)
# # return resp.content.decode()
# #
# # def parse_data(self, data):
# # # 解析响应,提取数据
# # results = json.loads(data)
# # # print(results)
# # results_list = results['subjects']
# # # 定义列表,用来保存提取的数据
# # data_list = []
# # # 遍历列表数据,获取每部电影的信息,名称,评分,url
# # for item in results_list:
# # temp = {}
# # temp['title'] = item['title']
# # temp['url'] = item['url']
# # data_list.append(temp)
# # return data_list
# #
# # def save_date(self, data_list):
# # # 保存数据
# # # 不能直接写入字典
# # for data in data_list:
# # # 不是ascii编码,在每条数据的后面,加上逗号和换行符
# # json_data = json.dumps(data, ensure_ascii=False) + '\n'
# # self.file.write(json_data)
# #
# # def __del__(self):
# # # 解构方法,最后执行(关闭文件)
# # self.file.close()
# #
# # def run(self):
# # data = self.get_data()
# # data_list = self.parse_data(data)
# # self.save_date(data_list)
# #
# #
# # if __name__ == '__main__':
# # douban = DouBan()
# # douban.run()
"""
http://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_web_fanyi&sign=2ad53c323affb5ad
POST
from: auto
to: en
q: 中国
content1
: # 中文翻译英文
{status: 1,…}
content:
{from: "zh", to: "en", vendor: "ciba", out: "China", reqid: "438b8144-4003-47fc-a40d-7f9956dea749",…}
status: 1
# 英文翻译中文
{status: 1,…}
content:
{from: "en", to: "zh", vendor: "ciba", out: "老鼠", reqid: "0cd50ed7-6c11-4b04-9c01-00f9ff1b1af4",…}
1.准备起始的url和请求头
2.发送请求,获取响应
3.解析响应,提取翻译结果
5.程序运行入口
"""
import requests
import json
class Translation(object):
def __init__(self, dw):
self.url = 'http://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_web_fanyi&sign' \
'=2ad53c323affb5ad '
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/110.0.0.0 Safari/537.36'}
self.data = {'from': 'zh', 'to': 'en', 'q': dw}
def get_data(self):
resp = requests.post(self.url, headers=self.headers, data=self.data)
return resp.content.decode('utf-8')
def parse_data(self, data):
# 把响应的json字符串改成字典,获取翻译结果
# 异常处理:
results = json.loads(data)
# try:
# 中文翻译成英文
result_list = results['content']['out']
# except:
# result_list = results['content']['out']
print(result_list)
def run(self):
data = self.get_data()
self.parse_data(data)
if __name__ == '__main__':
translation = Translation('翻译')
translation.run()
可以利用XPath,来快速的定位特定元素以及获取节点信息
XPATH(元素==标签==节点)
在HTML\XML文档中查找信息的语言,可以来在HTML\XML文档中对元素和属性进行遍历
lxml是python的模块,在python代码中书写xpath语言,定位网页数据。
xPath Helper:是个工具
# -------------------------
import re
from django.contrib.admin.templatetags.admin_list import results
# data = 'abc'
# print(re.findall('abc', data)) # ['abc']
# print(re.findall('a.c', data)) # ['abc']
# data = 'a\nc'
# print(re.findall('a.c', data)) # []
# # DOTALL表示可以匹配换行符
# data = 'a\nc'
# print(re.findall('a.c', data, re.DOTALL)) # ['a\nc']
# print(re.findall('a.c', data, re.S)) # ['a\nc']
# /斜线 \反斜线
# data = 'a\c'
# print(len(data))
# print(re.findall('a.c', data)) # ['a\\c']
# []字符集,范围内的字符
# data = 'abc adc afc'
# print(re.findall('a[bf]c', data))
# 预定义字符集
# data = 'itcast 2020 python36'
# 匹配字符串中的数字
# print(re.findall('\d', data)) # ['2', '0', '2', '0', '3', '6']
# # 匹配非数字
# print(re.findall('\D', data)) # ['i', 't', 'c', 'a', 's', 't', ' ', ' ', 'p', 'y', 't', 'h', 'o', 'n']
# # 匹配非空白字符
# print(re.findall('\S', data)) #['i', 't', 'c', 'a', 's', 't', '2', '0', '2', '0', 'p', 'y', 't', 'h', 'o', 'n', '3', '6']
# # 匹配空白字符
# print(re.findall('\s', data)) # [' ', ' ']
# # 匹配单词字符,字母,数字,下划线
# print(re.findall('\w',data)) #['i', 't', 'c', 'a', 's', 't', '2', '0', '2', '0', 'p', 'y', 't', 'h', 'o', 'n', '3', '6']
# print(re.findall('\W',data)) #[' ', ' ']
# 量词
data = 'itcast 你好2020 python36'
# *表示匹配前个字符0或n次
# print(re.findall('t*', data)) # ['', 't', '', '', '', 't', '', '', '', '', '', '', '', '', 't', '', '', '', '', '', '']
# print(re.findall('t+', data)) # ['t', 't', 't']
# 贪婪(尽可能多匹配)和非贪婪(限制匹配的次数,非贪婪)
# print(re.findall('t?', data)) # ['', 't', '', '', '', 't', '', '', '', '', '', '', '', '', 't', '', '', '', '', '', '']
# 爬虫代码中,多数情况下使用.*?
# print(re.findall('itcast(.*?)2020', data)) # [' 你好']
# compile表示编译
# 每执行一次findall,都会编译一次规则表达式,执行多次,编译多次
# print(re.findall('\d+', data))
# # 建议使用第二种,匹配效率比第一种要高
# r = re.compile('\d+')
# # 通过compile方法,得到编译的规则对象,直接调用findall,如果findall,执行多次只编写一次编译,多次使用
# print(r.findall(data))
"""
https://36kr.com/
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
实现步骤:
1.准备起始的url和请求头信息
2.发送请求,获取响应
3.解析响应,提取数据,返回提取的数据列表
4.保存数据列表
5.代码启动
36k新闻:提取标签,摘要,url地址,图片链接
"""
import requests
import re
class XinWen(object):
def __init__(self):
self.url = 'https://36kr.com/'
self.headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/110.0.0.0 Safari/537.36'}
self.file = open('xinwen2', 'w', encoding='utf-8')
def get_data(self):
resp = requests.get(self.url, headers=self.headers)
return resp.content.decode()
def parse_data(self, data):
results = re.findall('', data)[0]
# 把提取的原始新闻数据,保存文件,便于查看数据的结构
# with open('news.json', 'w', encoding='utf-8') as f:
# f.write(results)
# 把原始的新闻json数据,转成Python字典
result_dict = json.loads(results)
# 提取新闻列表数据
list_results = result_dict['homeData']['data']['homeFlow']['data']['itemList']
# 定义列表,用来保存提取的新闻数据
data_list = []
for item in list_results:
# 网页中itemType不同有10,60,5000
temp = {}
if item['itemType'] == 10:
temp['widgetTitle'] = item['templateMaterial']['widgetTitle']
temp['summary'] = item['templateMaterial']['summary']
temp['widgetImage'] = item['templateMaterial']['widgetImage']
temp['author'] = item['templateMaterial']['authorName']
print(temp)
data_list.append(temp)
return data_list
def save_data(self, data_list):
for data in data_list:
json_data = json.dumps(data, ensure_ascii=False, ) + '\n'
self.file.write(json_data)
def __del__(self):
self.file.close()
def run(self):
data = self.get_data()
data_list = self.parse_data(data)
self.save_data(data_list)
if __name__ == '__main__':
xinwen = XinWen()
xinwen.run()
网页html标签数据的体现形式:xPath默认提取的是标签中的文本内容
选中的标签会添加属性class='xh-highlight'
在爬虫代码中,xpath的使用,先从浏览器中copy,在手动修改xpath
# 数据保存在标签中
这是一个div
# 标签中的数据:‘这是一个a标签’
# 标签中的属性;href
< a href='...'>这是一个a标签
#绝对路径
/html/head/title
# 相对路径
//title
# 绝对路径:如果使用绝对路劲,任意层级发生变化,都会导致无法提取数据:
相对路径:爬虫代码中,建议使用相对路径
# 修饰属性:@根据属性查找具体的标签
/html/head/meta[@content='always']
# 修饰属性:@,提取标签中的属性内容
//div[@id='page']/div/a/@href
####提取下一页链接:不建议直接使用last,包含contains()
//div[@id='page']/div/a[last()]/@href
# 导入lxml中的etree,需要安装使用
# 代码中使用lxml:
# 1.首先通过浏览器复制xpath语句,在xpath_helper中测试能否提取数据
# 2.多数情况下后,需要修改复制xpath语句
# 3.把xpath语句,复制到代码中使用;
# 定义变量
import json
import requests
from lxml import etree
class DuanZi(object):
def __init__(self):
# 初始化url和请求头
self.url = 'http://haoduanzi.com/?1_{}'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/110.0.0.0 Safari/537.36'}
self.file = open('haoduanzi3.html', 'w', encoding='utf-8')
# 定义空列表,用来保存url
self.url_list = []
def generate_url_list(self):
# 生成不同页数的url信息
self.url_list = [self.url.format(i) for i in range(1, 10)]
def get_data(self, url):
resp = requests.get(url, headers=self.headers)
return resp.content.decode('utf-8')
def parse_data(self, data):
# 解析响应内容,提取段子的标题和内容
html = etree.HTML(data)
# not是官方自带函数
node_list = html.xpath("// *[ @ id = 'LR'] / div / div[2] / ul / li[not(@class='ad')]")
data_list = []
# 遍历节点列表,提取数据
for node in node_list:
temp = {}
temp['title'] = node.xpath('/div[1]/h2/text()')
temp['content'] = node.xpath('div[2]/a/p/text() | div[2]/a/text()')
data_list.append(temp)
# 返回列表
return data_list
def save_data(self, data_list):
# 遍历数据列表,把每条字典数据,转成json,保存文件
for data in data_list:
json_data = json.dumps(data, ensure_ascii=False) + '\n'
self.file.write(json_data)
def __del__(self):
self.file.close()
def run(self):
self.generate_url_list()
for url in self.url_list:
data = self.get_data(url)
data_list = self.parse_data(data)
self.save_data(data_list)
if __name__ == '__main__':
duanzi = DuanZi()
duanzi.run()
总结:
浏览器中测试的xpath能够取出页面,代码中不一定(lxml模块的语法和xpath在浏览器中的使用有区别);
如果代码中xpath提取不到,首先把响应写入文件,是否有数据,如果有数据,说明是lxml模块和xpath语法不同
把响应写入文件,是否有数据,如果没有数据,可能被网站反爬了,模拟浏览器更多的请求头:
"""
需求:
1.完成某个百度贴吧的所有贴
2.使用xpath进行定位
3.完成翻页功能
4.下载详情页面中的图片
1.第一次网络请求:div/div[2]/div[1]/div[1]/a/@href
https://tieba.baidu.com/f?ie=utf-8&kw=90%E5%90%8E%E7%BE%8E%E5%A5%B3
node_list=//*[@id="thread_list"]/li[@class="j_thread_list clearfix thread_item_box"]
提取每个帖子的链接,得到贴吧的列表,下一页的链接
https://tieba.baidu.com/ + div/div[2]/div[1]/div[1]/a/@href
下一页的链接,缺少https:/div[2]/div[1]/div[1]/a/@href
'https:'+ //*[@id="frs_list_pager"]/a[contains(text(),'下')]/@href
2.遍历帖子链接,发送请求,进入每个帖子中,提取图片列表
//img[@class="BDE_Image"]/@src
3.遍历图片列表,下载每张图片,保存图片
4.每一页图片处理完成后,翻页
"""
import requests
from lxml import etree
import os
class TuPian(object):
def __init__(self):
self.url = 'https://tieba.baidu.com/f?ie=utf-8&kw=90%E5%90%8E%E7%BE%8E%E5%A5%B3'
self.headers = {
# 'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
# 'Chrome/27.0.1453.94 Safari/537.36'}
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'}
def get_data(self, url):
resp = requests.get(url, headers=self.headers)
# 保存响应内容,用来测试
# with open('tupian11.html', 'w', encoding='utf-8') as f:
# f.write(resp.content.decode())
print(resp.content)
return resp.content
pass
def parse_data(self, data):
# 解析响应,返回帖子列表
html = etree.HTML(data)
node_list = html.xpath('//*[@id="thread_list"]/li[@class="j_thread_list clearfix thread_item_box"]')
print(len(node_list))
# 遍历列表
tieba_list = []
# 遍历节点列表,提取数据
for node in node_list:
temp = {}
temp['title_url'] = 'https://tieba.baidu.com/' + node.xpath('./div/div[2]/div[1]/div[1]/a/@href"]')[0]
print(temp)
tieba_list.append(temp)
# 提取下一页的链接,不能根据下标取链接,如果最后一页,为None
next_url = html.xpath("//*[@id='frs_list_pager']/a[contains(text(),'下一页')]/@href")
return node_list, next_url
pass
def parse_detail(self, datail_data):
# 解析帖子详情内容,返回图片列表
html = etree.HTML(datail_data)
image_list = html.xpath('//img[@class="BDE_Image"]/@src')
return image_list
def download(self, image_list):
# 遍历图片列表,发送请求,下载图片
# 通过代码,创建文件夹,用来保存图片,不能每
for img_url in image_list:
image_bytes = self.get_data(img_url)
image_name = 'image' + os.sep + img_url.split('/')[-1]
# 为了让代码,既可以
with open(image_name, 'wb', encoding='utf-8') as f:
f.write(image_bytes)
pass
def run(self):
# 第一次请求
url = self.url
# 解析响应内容,获取贴吧列表,下一页链接
while True:
data = self.get_data(url)
next_url, tieba_list = self.parse_data(data)
# 遍历贴吧列表,对每个帖子发送请求,获取图片列表
for tieba in tieba_list:
detail_data = self.get_data(tieba['url'])
image_list = self.parse_detail(detail_data)
self.download(image_list)
# 如果一个页面的贴吧列巴处理完,应该翻页
if not next_url:
break
else:
url = 'https:' + next_url
pass
if __name__ == '__main__':
tupian = TuPian()
tupian.run()