页面结构介绍 - HTLM常用标签了解
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
table - 表格
tr - 行
td - 列
-->
<table width="200px" height="200px" border="1px">
<tr>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</tr>
<tr>
<td>小明</td>
<td>18</td>
<td>男</td>
</tr>
</table>
<!-- ul li 无序列表-->
<ul>
<li>嘻嘻嘻</li>
<li>哈哈哈</li>
</ul>
<!-- ol li 有序列表-->
<ol>
<li>穿衣</li>
<li>洗漱</li>
<li>出门</li>
</ol>
<a href="https://livinfly.top">我的BLOG</a>
</body>
</html>
爬虫概念及步骤
用程序模拟浏览网页
核心
- 爬取网页
- 解析出需要的数据
- 爬虫和反爬虫
用途
- 数据分析/人工数据集
- 社交软件冷启动 什么是产品冷启动
- 舆论监控
- 竞争对手监控
- 出行、社交、电商、政府
分类
- 通用爬虫
- 不能根据需求准确爬取数据
- 聚焦爬虫
- 确定爬取url
- 模拟访问url,返回html
- 解析html代码
反爬手段
- UA - User-Agent
- 代理IP
- 验证码访问
- 动态加载网页
- 数据加密 urllib
- 定义要访问的url
- 模拟浏览器发送请求
- 解析 基本使用
# 以baidu首页为例
import urllib.request
# 定义url https需要加上提交表单!
url = 'http://www.baidu.com'
# 模拟放松请求
response = urllib.request.urlopen(url)
# 获取页面源码
# read返回字节形式的二进制数据
# 二进制->字符串 - 解码
content = response.read().decode('utf-8')
# 打印数据
print(content)
类型与方法
import urllib.request
url = 'http://www.baidu.com'
# 模拟发送请求
# 类型为HTTPResponse
response = urllib.request.urlopen(url)
# 一个字节一个字节读取
content = response.read()
# 读取5Byte
content = response.read(5)
# 读取一行
content = response.readline()
# 按行读取完
content = response.readlines()
# 返回状态码
# 200 - 正常
print(response.getcode())
# 返回url地址
print(response.geturl())
# 获取状态信息
print(response.getheaders())
下载
import urllib.request
url_page = 'http://www.baidu.com'
# 下载网页
urllib.request.urlretrieve(url_page, 'baidu.html')
# 下载图片
url_img = 'https://tse1-mm.cn.bing.net/th/id/OIP-C.PvWSTSmMfeE_TU6ZKG7f0wHaMl?pid=ImgDet&rs=1'
urllib.request.urlretrieve(url_img, 'pic.jpg')
# 下载视频
url_video = 'https://flv.bn.netease.com/596a4dc6201a2368b0661f28d722997680dcf23e8490551a7bfbf1c6d7f3b7b73c5945c81ae446fdd35a7f2b9e8fdd2df659302a82d165c79c8c0dcf844382f71c04da26c23b00a4a101f92f1cdd3ec8aa2d1c51fee00de074e740c48d77963465959304dbdbc0dfe3f4477874b971135cc86933b157ce3a.mp4'
urllib.request.urlretrieve(url_video, 'xxx.mp4')
请求对象的定制
import urllib.request
# 单口号
# http 80
# https 443
# mysql 3306
# oracle 1521
# redis 6379
# mongodb 27017
# url组成 - 协议+主机+端口号+路径+参数( = )+锚点(#)
url = 'https://www.baidu.com'
# 被https拒了 - 伪装UA - 浏览器,检查,网络,点击url,翻到UA
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 请求对象的定制,urlopen不能存储字典
# 顺序为url,data,...,关键字传参
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
get请求的quote方法
import urllib.request
import urllib.parse
# https://www.baidu.com/s?wd=%E5%91%A8%E6%9D%B0%E4%BC%A6
url = 'https://www.baidu.com/s?wd='
# 解决UA反爬
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 周杰伦->unicode by urllib.parse
name = urllib.parse.quote('周杰伦')
print(name)
url = url+name
# 请求对象定制
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
get请求的urlencode方法
应用场景:多个参数的时候
import urllib.request
import urllib.parse
base_url = 'https://www.baidu.com/s?'
data = {
'wd':'周杰伦',
'sex':'男',
'location':'中国台湾省'
}
new_data = urllib.parse.urlencode(data)
url = base_url + new_data
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
post请求 - 百度翻译
import urllib.request
import urllib.parse
# 寻找到接口
url = 'https://fanyi.baidu.com/sug'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
'kw':'spider'
}
# post的请求的参数 必须要进行编码+编码(先变为str, 再变为byte)
data = urllib.parse.urlencode(data).encode('utf-8')
# print(data)
# post的请求的参数,不加在url后面,而是在请求对象定制的参数中
request = urllib.request.Request(url=url, data=data, headers=headers)
# print(request)
# 模拟发送请求
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
print(type(content))
# str -> json
import json
obj = json.loads(content)
print(obj)
# post请求的参数要[编码]
# 编码再编码(先变为str, 再变为byte)
# 参数放在请求对象定制的方法中
案例2 - 百度翻译之详细翻译
import urllib.request
import urllib.parse
# 寻找到接口
url = 'https://fanyi.baidu.com/v2transapi?from=en&to=zh'
# cookie is best!! 不同网站有不同需求(?)or 再留下UA什么的
headers = {
'Cookie':'BIDUPSID=F685EAF03EB7C07D2AA6918F8B91F666; PSTM=1655776375; BDUSS=XlyMGRxMm1iSU56RzIyMjNkbWV6ejBLRUFabzZ-dmtySlRndnVsOWJUMlpUTmxpSVFBQUFBJCQAAAAAAAAAAAEAAACJJr9NxOO6w8Lwz8TM7MTjtcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJm~sWKZv7FiV; BAIDUID=F685EAF03EB7C07DC53B21ECE7C77EEA:SL=0:NR=10:FG=1; BDUSS_BFESS=XlyMGRxMm1iSU56RzIyMjNkbWV6ejBLRUFabzZ-dmtySlRndnVsOWJUMlpUTmxpSVFBQUFBJCQAAAAAAAAAAAEAAACJJr9NxOO6w8Lwz8TM7MTjtcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJm~sWKZv7FiV; BAIDU_WISE_UID=wapp_1656985477424_354; ZFY=yYOHI7x9G3fgtrG0wuizKjNxsXGVGtqa25mHaV6X5Z0:C; RT="z=1&dm=baidu.com&si=i4w8rva0dv&ss=l58atous&sl=2&tt=16a&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=1s7&ul=30q&hd=31m"; BA_HECTOR=0k8kal2l81002lal251hca3he14; BDRCVFR[-BxzrOzUsTb]=mk3SLVN4HKm; H_PS_PSSID=26350; BAIDUID_BFESS=53B1207E8EDBE4AA83C08A4F6075812C:FG=1; APPGUIDE_10_0_2=1; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; ab_sr=1.0.1_Y2ZlYWFhMzU4YTlkYmU0ZmU4NzAzN2E1N2VkOTE2ODk3MTcyYjdiOWU5MjlkYjU0NDMyMThjMWI5ZmZkOWU1ZDhkNDg1NmJjYmY1MTAwZjljNjk5N2Q1ZTI1NzNlZGQxMTFlNWI4NTVmMGI4OWQ4ZmZjZDM4OThhZjIzZGU3MTFhMzU3NTA3ZjI0ZmExYzYzYjA0NWY1YjUwYmIwMWU0ZTYwOTA3MmY3NjllYjBkYzI3NDJlMGEwZTdjYTQ2MWM5',
}
data = {
'from':'en',
'to':'zh',
'query':'parse',
'transtype':'realtime',
'simple_means_flag':'3',
'sign':'120721.341152',
'token':'ea747f5170ff35da81e5154d352c158f',
'domain':'common',
}
# 编码->str->byte
data = urllib.parse.urlencode(data).encode('utf-8')
# 请求对象定制
request = urllib.request.Request(url=url, data=data, headers=headers)
# 模拟请求浏览器
response = urllib.request.urlopen(request)
# 获取响应数据
content = response.read().decode('utf-8')
print(content)
import json
# str -> obj
obj = json.loads(content)
print(obj)
# 缺少完整请求头
ajax的get请求 - 豆瓣
第一页
前后端分离,所以后端返回json,再前端进行数据渲染
# get请求
# 获取豆瓣电影第一页的数据并保存
import urllib.request
import urllib.parse
url = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=0&limit=20'
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 请求对象定制
request = urllib.request.Request(url=url,headers=headers)
# 获取响应的数据
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
# 下载到本地 - 写到本地
# open方法默认gbk编码,想要保存别的编码使用
# encoding = 'utf-8‘
fp = open('douban.json', 'w', encoding='utf-8')
fp.write(content)
# 写完后可以格式化代码好看(快捷键ctrl+alt+L)与qq、滴答清单冲突
# 另一种形式 - 会自动关闭
with open('douban.json', 'w', encoding='utf-8') as fp:
fp.write(content)
ajax的post请求 - 肯德基官网
X-Request-With:XMLHttpRequest -- ajax请求
# 不同的页比较一下 发现不同页数主要是pageIndex不同
import urllib.request
import urllib.parse
def design_request(page):
url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
'cname':'北京',
'pid':'',
'pageIndex':page,
'pageSize':'10'
}
data = urllib.parse.urlencode(data).encode('utf-8')
request = urllib.request.Request(url=url,headers=headers, data=data)
return request
def get_content(request):
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
return content
def download_data(page, content):
with open('kfc_'+str(page)+'.json', 'w', encoding='utf-8') as fp:
fp.write(content)
# 使得别的文件调用该文件时,不会进行下面main的代码(因为name!=main)
if __name__ == '__main__':
start_page = int(input('输入开始页码'))
end_page = int(input('输入结束页码'))
for page in range(start_page, end_page+1):
# 定制请求
request = design_request(page)
# 获取源码
content = get_content(request)
# 保存数据
download_data(page, content)
异常
URL由协议、主机名、端口、路径、参数、锚点 URLError\HTTPError
后者时前者的子类 用try-except捕获异常
import urllib.request
import urllib.error
# url = 'https://blog.csdn.net/haojiagou/article/details/125653574'
# HTTPError
# url = 'https://blog.csdn.net/haojiagou/article/details/125653574123'
# URLError
url = 'http://www.hhahaissdifj.com'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
try:
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
except urllib.error.HTTPError:
print('网站不存在...')
except urllib.error.URLError:
print('2333333')
cookie登录 - 微博
适用场景:数据采集时需要绕过登录,进入某页面
个人信息页面时utf-8但还是报编码错误, 因为跳转到登录页面去了, 登陆页面不是utf-8 请求头信息不够
-> 访问不成功
cookie 携带登录信息 referer 判断防盗链-当前连接是不是由上一个路径进来 --- 一般为图片防盗链
import urllib.request
url = 'https://weibo.com/mygroups?gid=3864611478604000'
headers = {
'cookie':'******',
'referer':'******',
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
with open('wb.html', 'w', encoding='utf-8') as fp:
fp.write(content)
Handler处理器
为了处理如: 动态cookie\代理 定制更高级的请求头
基本使用 - 百度
import urllib.request
url = 'http://www.baidu.com'
headers = {
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)
# handler build_opener open
# [1] 获取handler对象
handler = urllib.request.HTTPHandler()
# [2] 获取opener对象
opener = urllib.request.build_opener(handler)
# [3] 调用open方法
response = opener.open(request)
content = response.read().decode('utf-8')
print(content)
代理
常用功能:
- 访问国外网站
- 访问内网资源(大学ftp)
- 提高访问速度, 代理服务器有较大的硬盘缓冲区, 不同用户访问相同资源速度快
- 隐藏真实IP
import urllib.request
url = 'http://www.baidu.com/s?wd=ip'
headers = {
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49',
}
request = urllib.request.Request(url=url,headers=headers)
# 免费代理(如快代理)
proxies = {
'http':'117.41.38.19:9000'
}
handler = urllib.request.ProxyHandler(proxies=proxies)
opener = urllib.request.build_opener(handler)
response = opener.open(request)
content = response.read().decode('utf-8')
with open('daili.html', 'w', encoding='utf-8') as fp:
fp.write(content)
代理池
# 先找到一堆有用的代理,然后随机出来
proxies_pool = [
{'http':'117.41.38.19:9000'},
{'http':'202.55.5.209:8090'},
]
import random
proxies = random.choice(proxies_pool)
print(proxies)
解析
xpath
- 安装并启用xpath插件 --- ctrl+shift+x启动
- 安装lxml库(python, 安装在你的python文件的解释器处) - pip install lxml -i https://pypi.douban.com/simple (豆瓣源)
解析对象:
- 本地文件 --- etree.parse
- 服务器响应的数据 --- etree.HTML()
严格遵守html形式
基本操作
from lxml import etree
# 解析本地文件
tree = etree.parse('html1.html')
# 1. 路径查找
# tree.xpath('xpath路径') ,“//”查找所有的子孙节点 “/”直接找子节点
# 查找ul下的li
li = tree.xpath('//body/ul/li')
# 2. 谓词查询
# 查找所有有id属性的li标签
# test() 获取标签中的内容
li = tree.xpath('//ul/li[@id]/text()')
# id l1的标签
li = tree.xpath('//ul/li[@id="l1"]/text()')
# 3. 属性查询 查找id为l1的li标签的class的属性值
li = tree.xpath('//ul/li[@id="l1"]/@class')
# 4. 模糊查找
li = tree.xpath('//ul/li[contains(@id,"l")]/text()')
li = tree.xpath('//ul/li[starts-with(@id,"l")]/text()')
# 5. 内容查询 /test()
# 6. 逻辑运算
# 查询id为li,class为c1
li = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')
li = tree.xpath('//ul/li[@id="l1" or @id="l2"]/text()')
li = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')
# 最后两个等价
print(li)
print(len(li))
案例1 - 获取百度网站的百度一下
import urllib.request
# 获得源码
url = 'https://www.baidu.com'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
#解析
from lxml import etree
tree = etree.HTML(content)
# (类型) id是唯一的(?
# 返回的是list
result = tree.xpath('//input[@id="su"]/@value')
print(result[0])
案例2 - 站长素材
下载前十页的图片
import urllib.request
from lxml import etree
def design_request(page):
if page == 1:
url = 'https://sc.chinaz.com/tupian/qinglvtupian.html'
else:
url = 'https://sc.chinaz.com/tupian/qinglvtupian_{}.html'.format(page)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
request = urllib.request.Request(url=url,headers=headers)
return request
def get_content(request):
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
return content
def download_to_local(content):
# urllib.request.urlretrieve('图片地址','文件名字')
tree = etree.HTML(content)
name_list = tree.xpath('//div[@id="container"]//a/img/@alt')
# 如果len为0, 一般涉及图片的网站会设计懒加载(在加载到之前为src2,之后为src)
# 可以直接定位到标签然后复制xpath路径
# 使用变之前的来爬取数据!!!
src_list = tree.xpath('//div[@id="container"]//a/img/@src')
for i in range(len(name_list)):
name = name_list[i]
src = src_list[i]
# 有些网站用'\' 可以使用url = url.replace('\\','/')
# 用url=url.replace('_s', '')就是高清图片,即删去 _s
url = 'https:'+src
url = url.replace('_s', '')
print(name, url)
urllib.request.urlretrieve(url=url,filename='./qinglu_pic/'+name+'.jpg')
if __name__ == '__main__':
start_page = int(input('请输入起始页码'))
end_page = int(input('请输入结束页码'))
for page in range(start_page,end_page+1):
request = design_request(page)
content = get_content(request)
download_to_local(content)
JsonPath
能解析本地文件
教程-简单入门
基本操作
所使用json文件
{ "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
import json
import jsonpath
# 读的是文件而非字符串
obj = json.load(open('json1.json','r',encoding='utf-8'))
# 书店所有书的作者
# '*'为通配符
author_list = jsonpath.jsonpath(obj,'$.store.book[*].author')
print(author_list)
# 所有的作者
author_list = jsonpath.jsonpath(obj,'$..author')
print(author_list)
# store下的所有元素
tag_list = jsonpath.jsonpath(obj,'$.store.*')
print(tag_list)
# store下所有的钱
price_list = jsonpath.jsonpath(obj,'$.store..price')
print(price_list)
# 第三个书
book = jsonpath.jsonpath(obj, '$..book[2]')
print(book)
# 最后一本书
book = jsonpath.jsonpath(obj, '$..book[(@.length-1)]')
print(book)
# 前两本书
book = jsonpath.jsonpath(obj, '$..book[0,1]')
# 或者[:2]
print(book)
# 过滤出所有包含isbn的书
# 条件过滤, 在 () 前面添加一个 ?
book_list = jsonpath.jsonpath(obj, '$..book[?(@.isbn)]')
print(book_list)
# 超过10$
book_list = jsonpath.jsonpath(obj, '$..book[?(@.price>10)')
print(book_list)
案例 - 解析淘票票
import urllib.request
url = 'https://dianying.taobao.com/cityAction.json?activityId&_ksTS=1657277832030_108&jsoncallback=jsonp109&action=cityAction&n_s=new&event_submit_doGetAllRegion=true'
# 这边请求头,经过测试只要有referer就可以了
headers = {
'referer': 'https://dianying.taobao.com/index.htm?spm=5049.7840664.0.0.3ad22dbfL6fH44&n_s=new',
}
request = urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
content = content.split('(')[1].split(')')[0]
print(content)
with open('tpp.json','w',encoding='utf-8') as fp:
fp.write(content)
import json
import jsonpath
obj = json.load(open('tpp.json','r',encoding='utf-8'))
city_list = jsonpath.jsonpath(obj,'$..regionName')
print(city_list)
BeautifulSoup - bs4
与lxml一样是一个html的解析器
本地和响应对象都可以解析
优缺点:
优点: 接口人性化, 使用方便(针对有基础的人)
缺点: 效率没有lxml高
基本使用
from bs4 import BeautifulSoup
# 解析本地
# 默认打开文件 编码格式为gbk
soup = BeautifulSoup(open('html1.html',encoding='utf-8'),'lxml')
# 1.根据标签查找节点
# 找到的是第一个符合条件的数据
print(soup.a)
# 2.获取标签的属性和属性值
print(soup.a.attrs)
# 函数
# 1.find
# 返回第一个符合条件的数据
print(soup.find('a'))
# 根据title找到对应的标签对象
print(soup.find('a', title="a2"))
# class在python内部有了,+ _
print(soup.find('a',class_="a1"))
# 2.find_all
# 返回list
print(soup.find_all('a'))
# 获取多个标签,用[]
print(soup.find_all(['a','span']))
# 前2个
print(soup.find_all('li',limit=2))
# 3.select(推荐)
# 返回list
print(soup.select('a'))
# 可以用.代替class --- 这种操作--类选择器
print(soup.select('.a1'))
# #代表id
print(soup.select('#l1'))
# 有id的li
print(soup.select('li[id]'))
# li中id为l2
print(soup.select('li[id="l2"]'))
# 层级选择器
# 后代选择器
# 找到div下的li
print(soup.select('div li'))
# 子代选择器(一级子标签
# 注意:bs4中可以不用写空格
print(soup.select('div > ul > li'))
# a与li的所有的对象
print(soup.select('a,li'))
# 节点信息
# 获取节点内容
obj = soup.select(('#d1'))[0]
# 若标签对象中 只有内容, string 和 get_text() 都可以使用
# 除了内容还有标签,string获取不到数据
# 推荐get_text()
print(obj.string)
print(obj.get_text())
# 节点的属性
obj = soup.select('#p1')[0]
# 标签名字
print(obj.name)
# 属性与属性值 字典
print(obj.attrs)
# 属性值
print(obj.attrs.get('class'))
print(obj.get('class'))
print(obj['class']) # 没有该属性是报错
案例 - 星巴克图片&名字
有人分享与1一个错误,取的名字不要与导入的包一致
import urllib.request
url = 'https://www.starbucks.com.cn/menu/'
# 先直接试试有没有反爬手段
response = urllib.request.urlopen(url)
content = response.read().decode('utf-8')
from bs4 import BeautifulSoup
soup = BeautifulSoup(content,'lxml')
# //ul[@class="grid padded-3 product"]//strong/text()
# 多个并列的class可以写出3ul.grid.padded-3.product 代替空格!!!
name_list = soup.select('.grid.padded-3.product strong')
name_list = soup.select('ul[class="grid padded-3 product"] strong')
for name in name_list:
print(name.string)
# print(name.get_text)
Selenium
用于web应用程序测试的工具 Selenium运行在浏览器中! 支持gezhongdriver驱动 支持无界面浏览器 但是比较慢 模拟浏览器功能,自动执行网页中的js代码, 实现动态加载
ps. 教程中因为用的是老版本的selenium,所以本人采用3.1410版本
为什么学它?
如京东, 首页的秒杀数据没有! 观众补充:seckill是由js渲染出来的,js要在浏览器中运行
安装selenium
- 下载一个谷歌浏览器驱动 - win32就行 - 解压后放在python文件目录下就行了
- 谷歌驱动和谷歌浏览器之间的映射表
- 查看谷歌的版本 - 帮助-关于
- pip install selenium 基本使用
from selenium import webdriver
# 创建浏览器操作对象
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)
# 访问网站
url = 'https://www.jd.com'
browser.get(url)
# page_source - 获取网页源码
content = browser.page_source
print(content)
元素定位
模拟鼠标和键盘来操作
from selenium import webdriver
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)
url = 'https://www.baidu.com'
browser.get(url)
# 元素定位
# 新版本:自行寻找
# 1.id 新版本:browser.find_element_by("id", "su")
button = browser.find_element_by_id('su')
print(button)
# 2.根据标签属性的属性值来获取对象
button = browser.find_element_by_name('wd')
print(button)
# 3.根据xpaht语句来获取对象
# 单个element(class),多个elements(list)
button = browser.find_elements_by_xpath('//input[@id="su"]')
print(button)
# 4.根据标签的名字来获取对象
button = browser.find_elements_by_tag_name('input')
print(button)
# 5.bs4语法
button = browser.find_elements_by_css_selector('#su')
print(button)
# 6.
button = browser.find_elements_by_link_text('新闻')
print(button)
元素信息
from selenium import webdriver
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)
url = 'https://www.baidu.com'
browser.get(url)
input = browser.find_element_by_id('su')
# 元素属性
print(input.get_attribute('class'))
# 标签名
print(input.tag_name)
# 元素文本??
a = browser.find_element_by_link_text('新闻')
print(a.text)
交互
from selenium import webdriver
# 创建浏览器对象
path = 'chromedriver.exe'
browser = webdriver.Chrome(path)
url = 'https://www.baidu.com'
browser.get(url)
import time
time.sleep(2)
# 获取文本框的对象
input = browser.find_element_by_id('kw')
# 在文本框输入周杰伦
input.send_keys('周杰伦')
# 获取按钮
button = browser.find_element_by_id('su')
# 点击按钮
button.click()
time.sleep(2)
# 到达底部
js_bottom = 'document.documentElement.scrollTop=100000'
browser.execute_script(js_bottom)
time.sleep(2)
# 获取下一页
next = browser.find_element_by_xpath('//a[@class="n"]')
next.click()
time.sleep(2)
# 回到上一页
browser.back()
time.sleep(2)
# 再回去
browser.forward()
time.sleep(3)
# 退出
browser.quit()
phantomjs
公司已经黄了(悲
和chromedriver一样,需要先把它的exe文件导过来
无界面浏览器,运行快
因为它基本凉了,我就没有自己动手再现视频了
from selenium import webdriver
path='phantomjs.exe'
browser = webdriver.PhantomJS(path)
url = 'https://www.baidu.com'
browser.get(url)
# 无界面浏览器 快照
browser.save_screenshot('baidu.png')
import time
time.sleep(2)
input = browser.find_element_by_id('kw')
input.send_keys('周杰伦')
time.sleep(3)
browser.save_screenshot('Jay.png')
Chrome handless
chrome的一种模式
要求:chrome >= 59/60
python 3.6
selenium 3.4.*
ChromeDriver 2.31
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# path修改为自己chrome浏览器的文件路径
path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
chrome_options.binary_location = path
# chrome_option 换成options了!!!
browser = webdriver.Chrome(options=chrome_options)
# 这上面是固定的,用的时候请直接复制,修改path即可
# 其他所有操作一致
url = 'https://www.baidu.com'
browser.get(url)
browser.save_screenshot('baidu.png')
# ---------------
# 封装的handless - 定成方法
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def share_browser():
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# path修改为自己chrome浏览器的文件路径
path = r'C:\Program Files\Google\Chrome\Application\chrome.exe'
chrome_options.binary_location = path
# chrome_option 换成options了!!!
browser = webdriver.Chrome(options=chrome_options)
return browser
browser = share_browser()
url = 'https://www.baidu.com'
browser.get(url)
requests
和urllib作用类似,但是部分业务用requests更简单
只属于python!
官方文档 快速上手 (这个网站好像倒了)(悲)
- 安装 pip install requests (可以加国内源)
基本使用
import requests
url = 'http://www.baidu.com'
response = requests.get(url=url)
# 一个类型和六个属性
# <class 'requests.models.Response'>
print(type(response))
# import urllib.request
# <class 'http.client.HTTPResponse'>
# print(type(urllib.request.urlopen(url)))
# 设置响应的编码格式
response.encoding = 'utf-8'
# 以字符串形式返回网站源码
print(response.text)
# 返回url地址
print(response.url)
# 返回二进制的数据
print(response.content)
# 返回响应的状态码
print(response.status_code)
# 返回响应头
print(response.headers)
get请求
- 参数用params传递
- 无需urlencode编码
- 不需要请求对象定制
- url里?可加可不加
import requests
# 问好可以加也可以不加
url = 'https://www.baidu.com/s?'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
'wd':'北京'
}
# url资源路径 params参数 kwargs字典
response = requests.get(url=url,params=data,headers=headers)
content = response.text
post请求
- 不需要编解码
- 请求的参数为data
- 不需要请求对象定制
import requests
# 问好可以加也可以不加
url = 'https://fanyi.baidu.com/sug'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
'kw': 'eye'
}
# url, data(参数), json, kwargs(字典)
response = requests.post(url=url,data=data,headers=headers)
content = response.text
print(content)
import json
# 不要加encoding防止报错!!
obj = json.loads(content)
print(obj)
代理
我这里遇到中文乱码(悲
import requests
url = 'https://www.baidu.com/s?'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
data = {
'wd':'ip'
}
proxy = {
'http':'202.55.5.209:8090'
}
response = requests.get(url=url,params=data,headers=headers,proxies=proxy)
content = response.text
print(content)
with open('daili.html','w',encoding='utf-8') as fp:
fp.write(content)
cookie - 验证码 - 以古诗文网为例
先寻找登录接口(输入错误的密码)找到需要的参数 一般有login _VIEWSTATE __VIEWSTATEGENEERATOR code是变量 [1]和[2],看不到的数据,一般在源码中,所以解析获取! [3]验证码
难点
隐藏域类似__xx
验证码
import requests
# 登录页面
url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
}
# 获得源码
response = requests.get(url=url,headers=headers)
content = response.text
# 参数可以在payload中找到
# 解析获得_VIEWSTATE ...
# 用bs4 or xpath
from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'lxml')
viewstate = soup.select('#__VIEWSTATE')[0].attrs.get('value')
viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0].attrs.get('value')
# 获取验证码图片
code = soup.select('#imgCode')[0].attrs.get('src')
code_url = 'https://so.gushiwen.cn'+code
# 有坑!!
# import urllib.request
# urllib.request.urlretrieve(url=code_url,filename='code.jpg')
# # 前后两次请求出来的code不一样!!
# session将请求的返回值变成一个对象
session = requests.session()
response_code = session.get(code_url)
# 二进制内容
content_code = response_code.content
with open('code.jpg','wb')as fp:
fp.write(content_code)
code_name = input('请输入你的验证码')
# preserve log勾上/不勾上 内容多不多
# 点击登录
url_post = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
data_post = {
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategenerator,
'from': 'http://so.gushiwen.cn/user/collect.aspx',
'email': 'rslovesgames@qq.com',
'pwd': 'qq123456',
'code': code_name,
'denglu': '登录',
}
response_post = session.post(url=url,headers=headers,data=data_post)
content_post = response_post.text
with open('gushiwen.html','w',encoding='utf-8')as fp:
fp.write(content_post)
超级鹰打码平台的使用
通过平台提供的技术来识别code
.get('pic_str')
scrapy
scrape+python=scrapy ???
提取结构性数据的应用框架
安装比较困难
记得用国内源
- pip install scrapy
- 报错
- 依赖的twisted没有(现在好像是会自动下载它),重安scrapy http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted找到对应系统的版本
- pip版本 - python -m pip install --upgrade pip
- win32错误 - pip install pypiwin32
- anacoda3-5.2.0 改变环境为anacoda的环境然后...
- 基本使用
用终端创建项目,且不能用数字开头,不包含中文 scrapy startproject + [filename]
cd 项目文件\项目文件\spiders
创建爬虫文件 scrapy genspider [fileName] [webpageName]
运行爬虫代码 scrapy crawl [spiderName]
有一个robot协议(君子协议),改一下settings中的ROBOTSTXT_OBEY = True改掉
import scrapy
class BaiduSpider(scrapy.Spider):
# 爬虫的名字 用于运行爬虫时候
name = 'baidu'
# 允许访问的域名
allowed_domains = ['www.baidu.com']
# 起始的url,即第一次要访问的域名
start_urls = ['http://www.baidu.com/']
# 执行了start_urls后 执行的方法,方法中response就是返回的对象
# 相当于 response = requests.get() // urllib.requeset.urlopen()
def parse(self, response):
print('hahahahaha')
58同城 - 项目结构和基本方法
spiders 爬虫文件
init
自定义 ******
items - 定义数据结构的地方 - 爬取的数据包含哪些 middleweare - 中间件 - 代理 pipelines - 管道 - 用来处理下载的数据 settings - 配置文件 - robots协议 - ua定义等
import scrapy
class TcSpider(scrapy.Spider):
name = 'tc'
# tz.58.com
allowed_domains = ['tz.58.com']
start_urls = ['http://tz.58.com']
def parse(self, response):
# response.body - 二进制数据
# response.text - str
# content = response.text
# print('==========================')
# print(content)
# 直接解析
aaa = response.xpath('/html/body/div[3]/div[1]/div[1]/div/div[3]/div[1]/div[4]/a')[0]
print('=============================')
# 提取seletor对象的(data)属性值
print(aaa.extract())
# .extract_first() 是提取seletor列表的第一个数据
汽车之家 - 工作原理
后缀是html时,不能加/ 因为网站改了+scrapy的版本不同了,也就没有本地再现
name_list = response.xpath(***/text())
price_list = ....
for i in range(len..):
print(..,..)
scrapy的架构组成
- 引擎,自动运行
- 下载器
- spiders
- 调度器
- 管道
工作原理
spiders --url-> 引擎 --url-> 调度器 --请求-> 下载器
--从互联网下载数据,数据(response)-> 引擎
--数据-> spiders(通过xpath解析数据) --解析结果-> 引擎
1. --url-> 引擎(循环)
2. --数据-> 管道(存到文件、数据库)
scrapy shell
Scrapy终端 - 免去每次修改后运行spider的麻烦
想要看到高亮,获得补全 - 安装ipython 在终端直接输入scrapy shell [webName]
然后在终端中进行和之前.py文件一样的操作
案例 - 当当网
spiders 爬虫文件
init
自定义 ******
items - 定义数据结构的地方 - 爬取的数据包含哪些
middleweare - 中间件 - 代理
pipelines - 管道 - 用来处理下载的数据
settings - 配置文件 - robots协议 - ua定义等
爬取数据
items.py
src = scrapy.Field()
name = scrapy.Field()
price = scrapy.Field()
dang.py
# pipelines - 下载数据
# items - 定义数据结构
# title = //ul[@class="bigimg"]/li/a/@title
# src = //ul[@class="bigimg"]/li/a/img/@src
# price = //ul[@class="bigimg"]/li/p[@class="price"]/span[1]/text()
# 可以用range(len)来取出来
# 所有seletor可以再次调用xpath
li_list = response.xpath('//ul[@class="bigimg"]/li')
for li in li_list:
title = li.xpath('./a/@title').extract_first()
# 图片懒加载找origanal
# 检查数据后发现,第一张没有origanal
src = li.xpath('./a/img/@data-original').extract_first()
if not src:
src = li.xpath('./a/img/@src').extract_first()
price = li.xpath('./p[@class="price"]/span[1]/text()').extract_first()
print(title,src,price)
管道封装
如果要使用管道,需要在settings中开启管道
settings.py
# ITEM_PIPELINES 的注释解开
# 管道可以有很多个 优先级为1->1000,值越小,优先级越高
pipelinse.py
class DangdangPipeline:
# 在爬虫文件执行前执行
def open_spider(self, spider):
# print('+++++++++++++++++++++++++')
self.fp = open('book.json','w',encoding='utf-8')
# item就是yield后面的book对象
def process_item(self, item, spider):
# 以下模式不推荐,传一个对象就就开一个对象,就开一次文件,操作过于频繁!!
# # 1.write写字符串
# # 2.w会把它覆盖
# with open('book.json','a',encoding='utf-8')as fp:
# fp.write(item)
self.fp.write(str(item))
return item
# 在爬虫文件执行后执行
def close_spider(self, spider):
# print('------------------------')
self.fp.close()
dang.py - 添加
from ..items import DangdangItem
# dangdang.items 会报错来着
book = DangdangItem(src=src,name=title,price=price)
# 获取一个book就将他传给pipelines
yield book
多条管道下载
pipelines.py - 添加
import urllib.request
# 多条管道开启
# [1] 定义管道类
# [2] 在settings中打开管道
class DangDangPipeline_Pic:
def process_item(self,item,spider):
url = 'http:'+item.get('src')
filename = './books/'+item.get('name')+'.jpg'
urllib.request.urlretrieve(url=url,filename=filename)
return item
模仿着写 settings.py
ITEM_PIPELINES = {
'dangdang.pipelines.DangdangPipeline': 300,
'dangdang.pipelines.DangDangPipeline_Pic':301,
}
多页下载
观察得到网页变化规律,因为爬取逻辑一样的调用parse!
dang.py + 在parse函数中
# 多页下载,必须要调整allowed_domains的范围 一般情况下只写域名
allowed_domains = ['category.dangdang.com']
start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']
base_url = 'http://category.dangdang.com/pg'
page = 1
# 下面的加在parse函数正后面
if self.page < 10:
self.page += 1
url = 'http://category.dangdang.com/pg{}-cp01.01.02.00.00.00.html'.format(self.page)
# 调用parse
# scrapy.Request是scrapy的get请求
# url请求地址
# callback要执行的函数,不加()
yield scrapy.Request(url=url,callback=self.parse)
案例 - 电影天堂多页下载
先看有没有反爬(直接print======
xpath拿不到数据,检查xpath语法正确性。 span不能识别!!
# 对第二页访问
# meta字典型, 为了让name和src在一起,传进去
...
yield scrapy.Request(url=url,callback=parse_second,meta={'name':name})
def parse_second(self,response):
# //div[@id="Zoom"]/span/img/@src
# 拿不到数据,检查xpath语法正确性。 span不能识别!!
src = response.xpath('//div[@id="Zoom"]//img/@src').extract_first()
name = response.meta['name']
movie = MovieHeavenItem(src=src,name=name)
yield movie
CrawlSpider - 连接提取器
- Mysql
pymysql
继承自scrapy.Spider
比如网站页码,可以知道链接,链接的解析规则一致
使用scrapy shell
from scrapy.linkexractors import LinkExtractor
# 正则表达式\d+ (\d 数字,+ 1-n个数字)
link = LinkExtractor(allow=r'/book/1188_\d+\.html')
link.extract_links(response)
link1 = LinkExtractor(restrict_xpaths=r'//div[@#class="pages"/a/@href')
link.extract_links(response)
# restrict_css
...
这scrapy个人感觉更偏向于企业级开发,不是特别感冒,就咕咕咕了... ^ ^