页面结构介绍 - 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.parsehttps://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.parsebase_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.parseurl = '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.parsedef 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.errorurl = '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 etreetree = etree.HTML(content)
(类型) id是唯一的(?
返回的是list
result = tree.xpath('//input[@id="su"]/@value')
print(result[0])
案例2 - 站长素材
下载前十页的图片
import urllib.request
from lxml import etreedef 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 Optionschrome_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 = pathchrome_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 Optionsdef 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 browserbrowser = 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.textprint(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列表的第一个数据</code></pre></div></div><h4 id="17sho" name="%E6%B1%BD%E8%BD%A6%E4%B9%8B%E5%AE%B6---%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86">汽车之家 - 工作原理</h4><p>后缀是html时,不能加/
因为网站改了+scrapy的版本不同了,也就没有本地再现
代码语言:javascript复制name_list = response.xpath(***/text())
price_list = ....
for i in range(len..):
print(..,..)
scrapy的架构组成
- 引擎,自动运行
- 下载器
- spiders
- 调度器
- 管道
工作原理
代码语言:javascript复制spiders --url-> 引擎 --url-> 调度器 --请求-> 下载器
--从互联网下载数据,数据(response)-> 引擎
--数据-> spiders(通过xpath解析数据) --解析结果-> 引擎
-
--url-> 引擎(循环)
--数据-> 管道(存到文件、数据库)
scrapy shell
Scrapy终端 - 免去每次修改后运行spider的麻烦
想要看到高亮,获得补全 - 安装ipython
在终端直接输入scrapy shell [webName]
然后在终端中进行和之前.py文件一样的操作
案例 - 当当网
代码语言:javascript复制spiders 爬虫文件
init
自定义 ******
items - 定义数据结构的地方 - 爬取的数据包含哪些
middleweare - 中间件 - 代理
pipelines - 管道 - 用来处理下载的数据
settings - 配置文件 - robots协议 - ua定义等
爬取数据
items.py
代码语言:javascript复制src = scrapy.Field()
name = scrapy.Field()
price = scrapy.Field()
dang.py
代码语言:javascript复制# 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)</code></pre></div></div><h5 id="avi34" name="%E7%AE%A1%E9%81%93%E5%B0%81%E8%A3%85">管道封装</h5><p><code>如果要使用管道,需要在settings中开启管道</code>
settings.py
代码语言:javascript复制# ITEM_PIPELINES 的注释解开
管道可以有很多个 优先级为1->1000,值越小,优先级越高
pipelinse.py
代码语言:javascript复制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()</code></pre></div></div><p><code>dang.py - 添加</code></p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">from ..items import DangdangItem
dangdang.items 会报错来着
book = DangdangItem(src=src,name=title,price=price)
# 获取一个book就将他传给pipelines
yield book
多条管道下载
pipelines.py - 添加
代码语言:javascript复制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
代码语言:javascript复制ITEM_PIPELINES = {
'dangdang.pipelines.DangdangPipeline': 300,
'dangdang.pipelines.DangDangPipeline_Pic':301,
}
多页下载
观察得到网页变化规律,因为爬取逻辑一样的调用parse!
dang.py + 在parse函数中
代码语言:javascript复制 # 多页下载,必须要调整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)</code></pre></div></div><h4 id="4i4go" name="%E6%A1%88%E4%BE%8B---%E7%94%B5%E5%BD%B1%E5%A4%A9%E5%A0%82%E5%A4%9A%E9%A1%B5%E4%B8%8B%E8%BD%BD">案例 - 电影天堂多页下载</h4><p>先看有没有反爬(直接print======</p><p><code>xpath拿不到数据,检查xpath语法正确性。 span不能识别!!</code></p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0"># 对第二页访问
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</code></pre></div></div><h4 id="8edb1" name="CrawlSpider---%E8%BF%9E%E6%8E%A5%E6%8F%90%E5%8F%96%E5%99%A8">CrawlSpider - 连接提取器</h4><ul class="ul-level-0"><li>Mysql</li></ul><ol class="ol-level-0"><li>下载</li><li>安装</li></ol><p>pymysql</p><p>继承自scrapy.Spider</p><p>比如网站页码,可以知道链接,链接的解析规则一致</p><p>使用scrapy shell</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">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个人感觉更偏向于企业级开发,不是特别感冒,就咕咕咕了... ^ ^