requests.get()
【1】作用
向目标url发起请求,并获取响应对象
【2】参数
2.1> url :需要抓取的URL地址
2.2> headers : 请求头
2.3> timeout : 超时时间,超过时间会抛出异常,例如:timeout=3
#打开浏览器,输入京东地址(https://www.baidu.com/),得到响应内容
import requests
res = requests.get('https://www.jd.com/')
html = res.text
print(html)
响应对象(res)属性
【1】text :字符串
【2】content :字节流
【3】status_code :HTTP响应码
【4】url :实际数据的URL地址
# 请求头(headers)中的 User-Agent
# 向测试网站http://httpbin.org/get发请求,查看请求头(User-Agent)
import requests
url = 'http://httpbin.org/get'
res = requests.get(url=url)
html = res.text
print(html)
问题解决
#养成好习惯,发送请求携带请求头,重构User-Agent
import requests
url = 'http://httpbin.org/get'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}
html = requests.get(url=url,headers=headers).text
print(html)
给URL地址中查询参数进行编码,参数类型为字典
使用方法
# 1、URL地址中 一 个查询参数
编码前: params = {'wd':'美女'}
编码中: params = urllib.parse.urlencode(params)
编码后: params结果: 'wd=%E7%BE%8E%E5%A5%B3'
# 2、URL地址中 多 个查询参数
编码前: params = {'wd':'美女','pn':'50'}
编码中: params = urllib.parse.urlencode(params)
编码后: params结果: 'wd=%E7%BE%8E%E5%A5%B3&pn=50'
发现编码后会自动对多个查询参数间添加 & 符号
拼接URL地址的三种方式
# url = 'http://www.baidu.com/s?'
# params = {'wd':'杨幂'}
# 问题: 请拼接出完整的URL地址
**********************************
params = urllib.parse.urlencode(params)
【1】字符串相加
【2】字符串格式化(占位符 %s)
【3】format()方法
'http://www.baidu.com/s?{}'.format(params)
案例练习
问题: 在百度网站中输入要搜索的内容,响应内容保存
编码方法使用 urlencode()
import requests
from urllib import parse
# 步骤1. 拼接URL地址
word = input('请输入搜索内容:')
params = parse.urlencode({'wd':word})
url = 'http://www.baidu.com/s?{}'
url = url.format(params)
# 步骤2.发请求获取响应内容
headers = {'User-Agent':'Mozilla/5.0'}
html = requests.get(url=url,headers=headers).content.decode('utf-8')
# 步骤 3. 保存到本地文件
filename = searchword + '.html'
with open(filename,'w',encoding='utf-8') as f:
f.write(html)
使用方法
http://www.baidu.com/s?wd=杨幂
# 对单独的字符串进行编码 - URL地址中的中文字符
word = '美女'
result = urllib.parse.quote(word)
result结果: ''
案例1
使用 quote()
在百度中输入要搜索的内容,把响应内容保存到本地文件
import requests
from urllib import parse
# 步骤1. 拼接URL地址
word = input('请输入搜索内容:')
params = parse.quote(word)
url = 'http://www.baidu.com/s?wd={}'
url = url.format(params)
# 步骤2. 发请求获取响应内容
headers = {'User-Agent':'Mozilla/5.0'}
html = requests.get(url=url,headers=headers).content.decode('utf-8')
# 步骤3. 保存到本地文件
filename = word + '.html'
with open(filename,'w',encoding='utf-8') as f:
f.write(html)
解码 unquote(string)
# 将编码后的字符串转为普通的Unicode字符串
from urllib import parse
params = '%E7%BE%8E%E5%A5%B3'
result = parse.unquote(params)
result结果: 美女
小总结
【1】 什么是robots协议
【2】 requests模块使用
res = requests.get(url=url,headers={'User-Agent':'xxx'})
响应对象res属性:
a> res.text
b> res.content
c> res.status_code
d> res.url
【3】urllib.parse
a> urlencode({'key1':'xxx','key2':'xxx'})
b> quote('xxx')
c> unquote('xxx')
【4】URL地址拼接 - urlencode()
a> 'http://www.baidu.com/s?' + params
b> 'http://www.baidu.com/s?%s' % params
c> 'http://www.baidu.com/s?{}'.format(params)
# 方法一
r_list=re.findall('正则表达式',html,re.S)
# 方法二
pattern = re.compile('正则表达式',re.S)
r_list = pattern.findall(html)
元字符 | 含义 |
---|---|
. | 任意一个字符(不包括\n) |
\d | 一个数字 |
\s | 空白字符 |
\S | 非空白字符 |
[] | 包含[]内容 |
* | 出现0次或多次 |
+ | 出现1次或多次 |
练习:
匹配任意一个字符的正则表达式
import re
# 方法一
pattern = re.compile('[\s\S]')
result = pattern.findall(html)
# 方法二
pattern = re.compile('.',re.S)
result = pattern.findall(html)
贪婪匹配(默认)
1、在整个表达式匹配成功的前提下,尽可能多的匹配 * + ?
2、表示方式:.* .+ .?
非贪婪匹配
1、在整个表达式匹配成功的前提下,尽可能少的匹配 * + ?
2、表示方式:.*? .+? .??
代码示例
import re
html = '''
九霄龙吟惊天变
风云际会潜水游
'''
# 贪婪匹配
p = re.compile('.*
',re.S)
r_list = p.findall(html)
print(r_list)
# 非贪婪匹配
p = re.compile('.*?
',re.S)
r_list = p.findall(html)
print(r_list)
作用
在完整的模式中定义子模式,将每个圆括号中子模式匹配出来的结果提取出来
示例代码
import re
s = 'A B C D'
p1 = re.compile('\w+\s+\w+')
print(p1.findall(s))
# 分析结果是什么???
# 结果:['A B', 'C D']
p2 = re.compile('(\w+)\s+\w+')
print(p2.findall(s))
# 第一步: ['A B', 'C D']
# 第二步: ['A', 'C']
p3 = re.compile('(\w+)\s+(\w+)')
print(p3.findall(s))
# 第一步: ['A B', 'C D']
# 第二步: [('A', 'B'), ('C', 'D')]
分组总结
1、在网页中,想要什么内容,就加()
2、先按整体正则匹配,然后再提取分组()中的内容
如果有2个及以上分组(),则结果中以元组形式显示 [(),(),()]
分析需求
【1】确定URL地址
百度搜索 - 猫眼电影 - 榜单 - top100榜
【2】 爬取目标
所有电影的 电影名称、主演、上映时间
电影名称、主演、上映时间
代码思路
【1】查看网页源码,确认数据来源
响应内容中存在所需抓取数据 - 电影名称、主演、上映时间
【2】翻页寻找URL地址规律
第1页:https://maoyan.com/board/4?offset=0
第2页:https://maoyan.com/board/4?offset=10
第n页:offset=(n-1)*10
【3】编写正则表达式
<div class="movie-item-info">.*?title="(.*?)".*?class="star">(.*?)</p>.*?releasetime">(.*?)</p>
参考代码
import requests
import re
import time
import random
class MaoyanSpider:
def __init__(self):
self.url = 'https://maoyan.com/board/4?offset={}'
self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko'}
def get_html(self, url):
html = requests.get(url=url, headers=self.headers).text
# 直接调用解析函数
self.parse_html(html)
def parse_html(self, html):
"""解析提取数据"""
regex = '.*?title="(.*?)".*?(.*?)
.*?(.*?)
'
pattern = re.compile(regex, re.S)
r_list = pattern.findall(html)
# r_list: [('活着','牛犇','2000-01-01'),(),(),...,()]
self.save_html(r_list)
def save_html(self, r_list):
"""数据处理函数"""
item = {}
for r in r_list:
item['name'] = r[0].strip()
item['star'] = r[1].strip()
item['time'] = r[2].strip()
print(item)
def run(self):
"""程序入口函数"""
for offset in range(0, 91, 10):
url = self.url.format(offset)
self.get_html(url=url)
# 控制数据抓取频率:uniform()生成指定范围内的浮点数
time.sleep(random.uniform(0,1))
if __name__ == '__main__':
spider = MaoyanSpider()
spider.run()
抓取数据存储到Mysql
import pymysql
db = pymysql.connect('localhost','root','123456','maoyandb',charset='utf8')
cursor = db.cursor()
ins = 'insert into filmtab values(%s,%s,%s)'
cursor.execute(ins,['xxx','xxxx','xxxx'])
db.commit()
cursor.close()
db.close()
-
案例 - 将电影信息存入MySQL数据库中
【1】创建库命令
create database maoyandb charset utf8;
use maoyandb;
create table maoyantab(
name varchar(100),
star varchar(300),
time varchar(100)
)charset=utf8;
【2】 使用excute()方法将数据存入数据库思路
2.1) 在 __init__() 中连接数据库并创建游标对象
2.2) 在 save_html() 中将所抓取的数据处理成列表,使用execute()方法写入
2.3) 在run() 中等数据抓取完成后关闭游标及断开数据库连接
数据存储为 - csv文件
【1】目的
将爬取的数据存放到本地的csv文件中
【2】使用流程步骤
2.1> 打开csv文件
2.2> 初始化写入对象
2.3> 写入数据(参数为列表)
【3】参考代码
import csv
with open('sky.csv','w') as f:
writer = csv.writer(f)
writer.writerow([])
-
案例代码
【1】题目描述
创建 www.csv 文件,在文件中写入数据
【2】数据写入 - writerow([])方法
import csv
with open('test.csv','w') as f:
writer = csv.writer(f)
writer.writerow(['超哥哥','25'])
-
实践1 - 使用 writerow() 方法将数据存入本地
【1】在 __init__() 中打开csv文件,因为csv文件只需要打开和关闭1次即可
【2】在 save_html() 中将所抓取的数据处理成列表,使用writerow()方法写入
【3】在run() 中等数据抓取完成后关闭文件
抓取(电影名称、主演、上映时间)
存入csv文件,使用writerow()方法
- 参考代码
import requests
import re
import time
import random
import csv
class MaoyanSpider:
def __init__(self):
self.url = 'https://maoyan.com/board/4?offset={}'
self.headers = {'User-Agent':'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)'}
# 打开文件,初始化写入对象
self.f = open('maoyan.csv', 'w', newline='', encoding='utf-8')
self.writer = csv.writer(self.f)
def get_html(self, url):
html = requests.get(url=url, headers=self.headers).text
# 直接调用解析函数
self.parse_html(html)
def parse_html(self, html):
"""解析提取数据"""
regex = '.*?title="(.*?)".*?(.*?)
.*?(.*?)
'
pattern = re.compile(regex, re.S)
r_list = pattern.findall(html)
# r_list: [('活着','牛犇','2000-01-01'),(),(),...,()]
self.save_html(r_list)
def save_html(self, r_list):
"""数据处理函数"""
for r in r_list:
li = [ r[0].strip(), r[1].strip(), r[2].strip() ]
self.writer.writerow(li)
print(li)
def run(self):
"""程序入口函数"""
for offset in range(0, 91, 10):
url = self.url.format(offset)
self.get_html(url=url)
# 控制数据抓取频率:uniform()生成指定范围内的浮点数
time.sleep(random.uniform(1,2))
# 所有数据抓取并写入完成后关闭文件
self.f.close()
if __name__ == '__main__':
spider = MaoyanSpider()
spider.run()
数据存储为 - MongoDB
-
特点
【1】非关系型数据库,数据以键值对方式存储,端口27017
【2】MongoDB基于磁盘存储
【3】MongoDB数据类型单一,值为JSON文档,而Redis基于内存,
3.1> MySQL数据类型:数值类型、字符类型、日期时间类型、枚举类型
3.2> Redis数据类型:字符串、列表、哈希、集合、有序集合
3.3> MongoDB数据类型:值为JSON文档
【4】MongoDB: 库 -> 集合 -> 文档
MySQL : 库 -> 表 -> 表记录
-
MongoDB常使用命令
show dbs - 查看所有库
use 库名 - 切换库
show collections - 查看当前库中所有集合
db.集合名.find().pretty() - 查看集合中文档
db.集合名.count() - 统计文档条数
db.集合名.drop() - 删除集合
db.dropDatabase() - 删除当前库
-
pymongo模块
import pymongo
# 1.连接对象
conn = pymongo.MongoClient(host = 'localhost',port = 27017)
# 2.库对象
db = conn['maoyandb']
# 3.集合对象
myset = db['maoyanset']
# 4.插入数据库
myset.insert_one({'name':'赵敏'})
-
练习2 - 将数据存入MongoDB数据库
参考代码:
import requests
import re
import time
import random
import pymongo
class MaoyanSpider:
def __init__(self):
self.url = 'https://maoyan.com/board/4?offset={}'
self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36'}
# 三个对象:连接对象、库对象、集合对象
self.conn = pymongo.MongoClient('127.0.0.1', 27017)
self.db = self.conn['maoyandb']
self.myset = self.db['maoyanset2']
def get_html(self, url):
html = requests.get(url=url, headers=self.headers).text
# 直接调用解析函数
self.parse_html(html)
def parse_html(self, html):
"""解析提取数据"""
regex = '.*?title="(.*?)".*?(.*?)
.*?(.*?)
'
pattern = re.compile(regex, re.S)
r_list = pattern.findall(html)
# r_list: [('活着','牛犇','2000-01-01'),(),(),...,()]
self.save_html(r_list)
def save_html(self, r_list):
"""数据处理函数"""
for r in r_list:
item = {}
item['name'] = r[0].strip()
item['star'] = r[1].strip()
item['time'] = r[2].strip()
print(item)
# 存入到mongodb数据库
self.myset.insert_one(item)
def run(self):
"""程序入口函数"""
for offset in range(0, 91, 10):
url = self.url.format(offset)
self.get_html(url=url)
# 控制数据抓取频率:uniform()生成指定范围内的浮点数
time.sleep(random.uniform(0,1))
if __name__ == '__main__':
spider = MaoyanSpider()
spider.run()
数据获取二级页面
-
步骤
【1】确定响应内容中是否存在所需数据 -是否 存在
【2】找URL地址规律
第1页: https://www.che168.com/beijing/a0_0msdgscncgpi1lto1csp1exx0/
第2页: https://www.che168.com/beijing/a0_0msdgscncgpi1lto1csp2exx0/
第n页: https://www.che168.com/beijing/a0_0msdgscncgpi1lto1csp{}exx0/
【3】 写正则表达式
一级页面正则表达式:<li class="cards-li list-photo-li".*?<a href="(.*?)".*?</li>
二级页面正则表达式:<div class="car-box">.*?<h3 class="car-brand-name">(.*?)</h3>.*?<ul class="brand-unit-item fn-clear">.*?<li>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<span class="price" id="overlayPrice">¥(.*?)<b>
【4】代码实现
<div class="car-box">.*?<h3 class="car-brand-name">(.*?)</h3>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<h4>(.*?)</h4>.*?<span class="price" id="overlayPrice">¥(.*?)<b>
-
参考代码
思路
1、一级页面:链接
2、二级页面:信息
使用fake_useragent模块
安装: pip3 install fake_useragent
from fake_useragent import UserAgent
UserAgent().random
import requests
import re
import time
import random
from fake_useragent import UserAgent
class CarSpider:
def __init__(self):
self.url = 'https://www.che168.com/beijing/a0_0msdgscncgpi1lto1csp{}exx0/'
def get_html(self, url):
"""功能函数1 - 获取html"""
headers = { 'User-Agent':UserAgent().random }
html = requests.get(url=url, headers=headers).text
return html
def re_func(self, regex, html):
"""功能函数2 - 正则解析函数"""
pattern = re.compile(regex, re.S)
r_list = pattern.findall(html)
return r_list
def parse_html(self, one_url):
"""爬虫逻辑函数"""
one_html = self.get_html(url=one_url)
one_regex = '- '
href_list = self.re_func(regex=one_regex, html=one_html)
for href in href_list:
two_url = 'https://www.che168.com' + href
# 获取1辆汽车的具体信息
self.get_car_info(two_url)
# 控制爬取频率
time.sleep(random.randint(1,2))
def get_car_info(self, two_url):
"""获取1辆汽车的具体信息"""
two_html = self.get_html(url=two_url)
two_regex = '.*?(.*?)
.*?(.*?)
.*?(.*?)
.*?(.*?)
.*?(.*?)
.*?¥(.*?)'
# car_list: [('xxx','3万公里','2年3月','xx / 1.5L', '廊坊', '5.60'),]
car_list = self.re_func(regex=two_regex, html=two_html)
item = {}
item['name'] = car_list[0][0].strip()
item['km'] = car_list[0][1].strip()
item['time'] = car_list[0][2].strip()
item['type'] = car_list[0][3].split('/')[0].strip()
item['displace'] = car_list[0][3].split('/')[1].strip()
item['address'] = car_list[0][4].strip()
item['price'] = car_list[0][5].strip()
print(item)
def run(self):
for i in range(1,5):
url = self.url.format(i)
self.parse_html(url)
if __name__ == '__main__':
spider = CarSpider()
spider.run()
-
实现增量爬虫 Redis
提示: 使用redis中的集合,sadd()方法,添加成功返回1,否则返回0
import requests
import re
import time
import random
import pymysql
from hashlib import md5
import sys
import redis
class CarSpider(object):
def __init__(self):
self.url = 'https://www.che168.com/beijing/a0_0msdgscncgpi1lto1csp{}exx0/'
self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1'}
self.db = pymysql.connect('localhost','root','123456','cardb',charset='utf8')
self.cursor = self.db.cursor()
# 连接redis去重
self.r = redis.Redis(host='localhost',port=6379,db=0)
# 功能函数1 - 获取响应内容
def get_html(self,url):
html = requests.get(url=url,headers=self.headers).text
return html
# 功能函数2 - 正则解析
def re_func(self,regex,html):
pattern = re.compile(regex,re.S)
r_list = pattern.findall(html)
return r_list
# 爬虫函数开始
def parse_html(self,one_url):
one_html = self.get_html(one_url)
one_regex = '- '
href_list = self.re_func(one_regex,one_html)
for href in href_list:
# 加密指纹
s = md5()
s.update(href.encode())
finger = s.hexdigest()
# 如果指纹表中不存在
if self.r.sadd('car:urls',finger):
# 每便利一个汽车信息,必须要把此辆汽车所有数据提取完成后再提取下一辆汽车信息
url = 'https://www.che168.com' + href
# 获取一辆汽车的信息
self.get_data(url)
time.sleep(random.randint(1,2))
else:
sys.exit('抓取结束')
# 判断是否存在:存在返回False,不存在返回True
def go_spider(self,finger):
sel = 'select * from request_finger where finger=%s'
result = self.cursor.execute(sel,[finger])
if result:
return False
return True
# 获取一辆汽车信息
def get_data(self,url):
two_html = self.get_html(url)
two_regex = '.*?(.*?)
.*?.*?- .*?
(.*?)
.*?(.*?)
.*?(.*?)
.*?(.*?)
.*?¥(.*?)
item = {}
car_info_list = self.re_func(two_regex,two_html)
item['name'] = car_info_list[0][0]
item['km'] = car_info_list[0][1]
item['year'] = car_info_list[0][2]
item['type'] = car_info_list[0][3].split('/')[0]
item['displacement'] = car_info_list[0][3].split('/')[1]
item['city'] = car_info_list[0][4]
item['price'] = car_info_list[0][5]
print(item)
one_car_list = [
item['name'],
item['km'],
item['year'],
item['type'],
item['displacement'],
item['city'],
item['price']
]
ins = 'insert into cartab values(%s,%s,%s,%s,%s,%s,%s)'
self.cursor.execute(ins,one_car_list)
self.db.commit()
def run(self):
for p in range(1,2):
url = self.url.format(p)
self.parse_html(url)
# 断开数据库链接
self.cursor.close()
self.db.close()
if __name__ == '__main__':
spider = CarSpider()
spider.run()
xpath解析
-
定义
XPath即为XML路径语言,它是一种用来确定XML文档中某部分位置的语言,同样适用于HTML文档的检索
【1】查找所有的dd节点
//dd
【2】获取所有电影的名称的a节点: 所有class属性值为name的a节点
//p[@class="name"]/a
【3】获取dl节点下第2个dd节点的电影节点
//dl[@class="board-wrapper"]/dd[2]
【4】获取所有电影详情页链接: 获取每个电影的a节点的href的属性值
//p[@class="name"]/a/@href
【注意】
1> 只要涉及到条件,加 [] : //dl[@class="xxx"] //dl/dd[2]
2> 只要获取属性值,加 @ : //dl[@class="xxx"] //p/a/@href
-
选取节点
【1】// : 从所有节点中查找(包括子节点和后代节点)
【2】@ : 获取属性值
2.1> 使用场景1(属性值作为条件)
//div[@class="movie-item-info"]
2.2> 使用场景2(直接获取属性值)
//div[@class="movie-item-info"]/a/img/@src
【3】练习 - 猫眼电影top100
3.1> 匹配电影名称
//div[@class="movie-item-info"]/p[1]/a/@title
3.2> 匹配电影主演
//div[@class="movie-item-info"]/p[2]/text()
3.3> 匹配上映时间
//div[@class="movie-item-info"]/p[3]/text()
3.4> 匹配电影链接
//div[@class="movie-item-info"]/p[1]/a/@href
-
匹配多路径(或)
xpath表达式1 | xpath表达式2 | xpath表达式3
-
常用函数
【1】text() :获取节点的文本内容
xpath表达式末尾不加 /text() :则得到的结果为节点对象
xpath表达式末尾加 /text() 或者 /@href : 则得到结果为字符串
【2】contains() : 匹配属性值中包含某些字符串节点
匹配class属性值中包含 'movie-item' 这个字符串的 div 节点
//div[contains(@class,"movie-item")]
-
总结
【1】字符串: xpath表达式的末尾为: /text() 、/@href 得到的列表中为'字符串'
【2】节点对象: 其他剩余所有情况得到的列表中均为'节点对象'
[<element dd at xxxa>,<element dd at xxxb>,<element dd at xxxc>]
[<element div at xxxa>,<element div at xxxb>]
[<element p at xxxa>,<element p at xxxb>,<element p at xxxc>]
lxml解析库
-
安装
【1】 pip3 install lxml
【2】Windows: python -m pip install lxml
-
使用流程
1、导模块
from lxml import etree
2、创建解析对象
parse_html = etree.HTML(html)
3、解析对象调用xpath
r_list = parse_html.xpath('xpath表达式')
-
xpath最常用
【1】基准xpath: 匹配所有电影信息的节点对象列表
//dl[@class="board-wrapper"]/dd
[<element dd at xxx>,<element dd at xxx>,...]
【2】遍历对象列表,依次获取每个电影信息
item = {}
for dd in dd_list:
item['name'] = dd.xpath('.//p[@class="name"]/a/text()').strip()
item['star'] = dd.xpath('.//p[@class="star"]/text()').strip()
item['time'] = dd.xpath('.//p[@class="releasetime"]/text()').strip()
-
目前反爬处理
【1】基于User-Agent反爬
1.1) 发送请求携带请求头: headers={'User-Agent' : 'Mozilla/5.0 xxxxxx'}
1.2) 多个请求时随机切换User-Agent
a) 定义py文件存放大量User-Agent,导入后使用random.choice()每次随机选择
b) 使用fake_useragent模块每次访问随机生成User-Agent
from fake_useragent import UserAgent
agent = UserAgent().random
【2】响应内容存在特殊字符
解码时使用ignore参数
html = requests.get(url=url, headers=headers).content.decode('', 'ignore')
-
匹配多路径(或)
xpath表达式1 | xpath表达式2 | xpath表达式3
-
常用函数
【1】text() :获取节点的文本内容
xpath表达式末尾不加 /text() :则得到的结果为节点对象
xpath表达式末尾加 /text() 或者 /@href : 则得到结果为字符串
【2】contains() : 匹配属性值中包含某些字符串节点
匹配class属性值中包含 'movie-item' 这个字符串的 div 节点
//div[contains(@class,"movie-item")]
-
xpath最常用
【1】基准xpath: 匹配所有电影信息的节点对象列表
//dl[@class="board-wrapper"]/dd
[<element dd at xxx>,<element dd at xxx>,...]
【2】遍历对象列表,依次获取每个电影信息
item = {}
for dd in dd_list:
item['name'] = dd.xpath('.//p[@class="name"]/a/text()').strip()
item['star'] = dd.xpath('.//p[@class="star"]/text()').strip()
item['time'] = dd.xpath('.//p[@class="releasetime"]/text()').strip()
xpath**
-
需求分析
【1】抓取目标 - 豆瓣图书top250的图书信息
https://book.douban.com/top250?start=0
https://book.douban.com/top250?start=25
https://book.douban.com/top250?start=50
... ...
【2】抓取数据
2.1) 书籍名称 :红楼梦
2.2) 书籍描述 :[清] 曹雪芹 著 / 人民文学出版社 / 1996-12 / 59.70元
2.3) 书籍评分 :9.6
2.4) 评价人数 :286382人评价
2.5) 书籍类型 :都云作者痴,谁解其中味?
-
步骤分析
【1】确认数据来源 - 响应内容存在
【2】分析URL地址规律 - start为0 25 50 75 ...
【3】xpath表达式
3.1) 基准xpath,匹配每本书籍的节点对象列表
//div[@class="indent"]/table
3.2) 依次遍历每本书籍的节点对象,提取具体书籍数据
书籍名称 : .//div[@class="pl2"]/a/@title
书籍描述 : .//p[@class="pl"]/text()
书籍评分 : .//span[@class="rating_nums"]/text()
评价人数 : .//span[@class="pl"]/text()
书籍类型 : .//span[@class="inq"]/text()
-
参考代码
import requests
from lxml import etree
from fake_useragent import UserAgent
import time
import random
class DoubanBookSpider:
def __init__(self):
self.url = 'https://book.douban.com/top250?start={}'
def get_html(self, url):
"""使用随机的User-Agent"""
headers = {'User-Agent':UserAgent().random}
html = requests.get(url=url, headers=headers).text
self.parse_html(html)
def parse_html(self, html):
"""lxml+xpath进行数据解析"""
parse_obj = etree.HTML(html)
# 1.基准xpath:提取每本书的节点对象列表
table_list = parse_obj.xpath('//div[@class="indent"]/table')
for table in table_list:
item = {}
# 书名
name_list = table.xpath('.//div[@class="pl2"]/a/@title')
item['name'] = name_list[0].strip() if name_list else None
# 描述
content_list = table.xpath('.//p[@class="pl"]/text()')
item['content'] = content_list[0].strip() if content_list else None
# 评分
score_list = table.xpath('.//span[@class="rating_nums"]/text()')
item['score'] = score_list[0].strip() if score_list else None
# 评价人数
nums_list = table.xpath('.//span[@class="pl"]/text()')
item['nums'] = nums_list[0][1:-1].strip() if nums_list else None
# 类别
type_list = table.xpath('.//span[@class="inq"]/text()')
item['type'] = type_list[0].strip() if type_list else None
print(item)
def run(self):
for i in range(5):
start = (i - 1) * 25
page_url = self.url.format(start)
self.get_html(page_url)
time.sleep(random.randint(1,2))
if __name__ == '__main__':
spider = DoubanBookSpider()
spider.run()
requests.post()**
-
适用场景
【1】适用场景 : Post类型请求的网站
【2】参数 : data={}
2.1) Form表单数据: 字典
2.2) res = requests.post(url=url,data=data,headers=headers)
【3】POST请求特点 : Form表单提交数据
控制台抓包
-
打开方式及常用选项
【1】打开浏览器,F12打开控制台,找到Network选项卡
【2】控制台常用选项
2.1) Network: 抓取网络数据包
a> ALL: 抓取所有的网络数据包
b> XHR:抓取异步加载的网络数据包
c> JS : 抓取所有的JS文件
2.2) Sources: 格式化输出并打断点调试JavaScript代码,助于分析爬虫中一些参数
2.3) Console: 交互模式,可对JavaScript中的代码进行测试
【3】抓取具体网络数据包后
3.1) 单击左侧网络数据包地址,进入数据包详情,查看右侧
3.2) 右侧:
a> Headers: 整个请求信息
General、Response Headers、Request Headers、Query String、Form Data
b> Preview: 对响应内容进行预览
c> Response:响应内容
有道翻译破解案例(post)
-
目标
破解有道翻译接口,抓取翻译结果
结果展示
请输入要翻译的词语: elephant
翻译结果: 大象
请输入要翻译的词语: 喵喵叫
翻译结果: mews
-
步骤
【1】浏览器F12开启网络抓包,Network-All,页面翻译单词后找Form表单数据
【2】在页面中多翻译几个单词,观察Form表单数据变化(有数据是加密字符串)
【3】刷新有道翻译页面,抓取并分析JS代码(本地JS加密)
【4】找到JS加密算法,用Python按同样方式加密生成加密数据
【5】将Form表单数据处理为字典,通过requests.post()的data参数发送
1、开启F12抓包,找到Form表单数据如下:
i: 施工方第三个
from: AUTO
to: AUTO
smartresult: dict
client: fanyideskweb
salt: 15614112641250
sign: 94008208919faa19bd531acde36aac5d
ts: 1561411264125
bv: f4d62a2579ebb44874d7ef93ba47e822
doctype: json
version: 2.1
keyfrom: fanyi.web
action: FY_BY_REALTlME
2、在页面中多翻译几个单词,观察Form表单数据变化
salt: 15614112641250
sign: 94008208919faa19bd531acde36aac5d
ts: 1561411264125
bv: f4d62a2579ebb44874d7ef93ba47e822
# 但是bv的值不变
3、一般为本地js文件加密,刷新页面,找到js文件并分析JS代码
【方法1】 : Network - JS选项 - 搜索关键词salt
【方法2】 : 控制台右上角 - Search - 搜索salt - 查看文件 - 格式化输出
【结果】 : 最终找到相关JS文件 : fanyi.min.js
4、打开JS文件,分析加密算法,用Python实现
【ts】经过分析为13位的时间戳,字符串类型
js代码实现) "" + (new Date).getTime()
python实现) str(int(time.time()*1000))
【salt】
js代码实现) ts + parseInt(10 * Math.random(), 10);
python实现) ts + str(random.randint(0,9))
【sign】('设置断点调试,来查看 e 的值,发现 e 为要翻译的单词')
js代码实现) n.md5("fanyideskweb" + e + salt + "n%A-rKaT5fb[Gy?;N5@Tj")
python实现)
from hashlib import md5
string = "fanyideskweb" + e + salt + "n%A-rKaT5fb[Gy?;N5@Tj"
s = md5()
s.update(string.encode())
sign = s.hexdigest()
4、pycharm中正则处理headers和formdata
【1】pycharm进入方法 :Ctrl + r ,选中 Regex
【2】处理headers和formdata
(.*): (.*)
"$1": "$2",
【3】点击 Replace All
5、代码实现
import requests
import time
import random
from hashlib import md5
class YdSpider(object):
def __init__(self):
# url一定为F12抓到的 headers -> General -> Request URL
self.url = 'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
self.headers = {
# 检查频率最高 - 3个
"Cookie": "[email protected]; OUTFOX_SEARCH_USER_ID_NCOO=570559528.1224236; _ntes_nnid=96bc13a2f5ce64962adfd6a278467214,1551873108952; JSESSIONID=aaae9i7plXPlKaJH_gkYw; td_cookie=18446744072941336803; SESSION_FROM_COOKIE=unknown; ___rl__test__cookies=1565689460872",
"Referer": "http://fanyi.youdao.com/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
}
# 获取salt,sign,ts
def get_salt_sign_ts(self,word):
# ts
ts = str(int(time.time()*1000))
# salt
salt = ts + str(random.randint(0,9))
# sign
string = "fanyideskweb" + word + salt + "n%A-rKaT5fb[Gy?;N5@Tj"
s = md5()
s.update(string.encode())
sign = s.hexdigest()
return salt,sign,ts
# 主函数
def attack_yd(self,word):
# 1. 先拿到salt,sign,ts
salt,sign,ts = self.get_salt_sign_ts(word)
# 2. 定义form表单数据为字典: data={}
# 检查了salt sign
data = {
"i": word,
"from": "AUTO",
"to": "AUTO",
"smartresult": "dict",
"client": "fanyideskweb",
"salt": salt,
"sign": sign,
"ts": ts,
"bv": "7e3150ecbdf9de52dc355751b074cf60",
"doctype": "json",
"version": "2.1",
"keyfrom": "fanyi.web",
"action": "FY_BY_REALTlME",
}
# 3. 直接发请求:requests.post(url,data=data,headers=xxx)
html = requests.post(
url=self.url,
data=data,
headers=self.headers
).json()
# res.json() 将json格式的字符串转为python数据类型
result = html['translateResult'][0][0]['tgt']
print(result)
# 主函数
def run(self):
# 输入翻译单词
word = input('请输入要翻译的单词:')
self.attack_yd(word)
if __name__ == '__main__':
spider = YdSpider()
spider.run()
-
百度翻译-js逆向
【1】execjs模块相关
1.1》安装: pip3 install pyexecjs
1.2》js环境安装: yum -y install nodejs
【2】execjs模块使用
import execjs
with open('xxx.js', 'r') as f:
js_code = f.read()
pattern = execjs.compile(js_code)
pattern.eval('js中函数')
【3】百度翻译流程梳理
3.1》F12抓包,找到相关网络数据包,Form表单中发现sign
3.2》查找相关js文件,并找到相关js代码
sign: y(r)
token: window.common.token
3.3》断点调试,进入到y(r)函数,发现加密的js代码,很复杂
3.4》把相关js代码复制到 translate.js 文件中,利用execjs模块进行调试
增加参数u: e(r, u)
注释原来u: // var u = ... ...
3.5》写代码
动态加载数据抓取-Ajax
-
特点
【1】右键 -> 查看网页源码中没有具体数据
【2】滚动鼠标滑轮或其他动作时加载,或者页面局部刷新
-
抓取
【1】F12打开控制台,页面动作抓取网络数据包
【2】抓取json文件URL地址
2.1) 控制台中 XHR :异步加载的数据包
2.2) XHR -> QueryStringParameters(查询参数)
豆瓣电影数据抓取案例
-
目标
【1】地址: 豆瓣电影 - 排行榜 - 剧情
【2】目标: 电影名称、电影评分
-
F12抓包(XHR)
【1】Request URL(基准URL地址) :https://movie.douban.com/j/chart/top_list?
【2】Query String(查询参数)
# 抓取的查询参数如下:
type: 13 # 电影类型
interval_id: 100:90
action: ''
start: 0 # 每次加载电影的起始索引值 0 20 40 60
limit: 20 # 每次加载的电影数量
-
代码实现 - 全站抓取
"""
豆瓣电影 - 全站抓取
"""
import requests
from fake_useragent import UserAgent
import time
import random
import re
import json
class DoubanSpider:
def __init__(self):
self.url = 'https://movie.douban.com/j/chart/top_list?'
self.i = 0
# 存入json文件
self.f = open('douban.json', 'w', encoding='utf-8')
self.all_film_list = []
def get_agent(self):
"""获取随机的User-Agent"""
return UserAgent().random
def get_html(self, params):
headers = {'User-Agent':self.get_agent()}
html = requests.get(url=self.url, params=params, headers=headers).text
# 把json格式的字符串转为python数据类型
html = json.loads(html)
self.parse_html(html)
def parse_html(self, html):
"""解析"""
# html: [{},{},{},{}]
item = {}
for one_film in html:
item['rank'] = one_film['rank']
item['title'] = one_film['title']
item['score'] = one_film['score']
print(item)
self.all_film_list.append(item)
self.i += 1
def run(self):
# d: {'剧情':'11','爱情':'13','喜剧':'5',...,...}
d = self.get_d()
# 1、给用户提示,让用户选择
menu = ''
for key in d:
menu += key + '|'
print(menu)
choice = input('请输入电影类别:')
if choice in d:
code = d[choice]
# 2、total: 电影总数
total = self.get_total(code)
for start in range(0,total,20):
params = {
'type': code,
'interval_id': '100:90',
'action': '',
'start': str(start),
'limit': '20'
}
self.get_html(params=params)
time.sleep(random.randint(1,2))
# 把数据存入json文件
json.dump(self.all_film_list, self.f, ensure_ascii=False)
self.f.close()
print('数量:',self.i)
else:
print('请做出正确的选择')
def get_d(self):
"""{'剧情':'11','爱情':'13','喜剧':'5',...,...}"""
url = 'https://movie.douban.com/chart'
html = requests.get(url=url,headers={'User-Agent':self.get_agent()}).text
regex = ''
pattern = re.compile(regex, re.S)
# r_list: [('剧情','11'),('喜剧','5'),('爱情':'13')... ...]
r_list = pattern.findall(html)
# d: {'剧情': '11', '爱情': '13', '喜剧': '5', ..., ...}
d = {}
for r in r_list:
d[r[0]] = r[1]
return d
def get_total(self, code):
"""获取某个类别下的电影总数"""
url = 'https://movie.douban.com/j/chart/top_list_count?type={}&interval_id=100%3A90'.format(code)
html = requests.get(url=url,headers={'User-Agent':self.get_agent()}).text
html = json.loads(html)
return html['total']
if __name__ == '__main__':
spider = DoubanSpider()
spider.run()
json解析模块
-
json.loads(json)
【1】作用 : 把json格式的字符串转为Python数据类型
【2】示例 : html = json.loads(res.text)
-
json.dump(python,f,ensure_ascii=False)
【1】作用
把python数据类型 转为 json格式的字符串,一般让你把抓取的数据保存为json文件时使用
【2】参数说明
2.1) 第1个参数: python类型的数据(字典,列表等)
2.2) 第2个参数: 文件对象
2.3) 第3个参数: ensure_ascii=False 序列化时编码
【3】示例代码
# 示例1
import json
item = {'name':'QQ','app_id':1}
with open('小米.json','a') as f:
json.dump(item,f,ensure_ascii=False)
# 示例2
import json
item_list = []
for i in range(3):
item = {'name':'QQ','id':i}
item_list.append(item)
with open('xiaomi.json','a') as f:
json.dump(item_list,f,ensure_ascii=False)
-
json模块总结
# 爬虫最常用
【1】数据抓取 - json.loads(html)
将响应内容由: json 转为 python
【2】数据保存 - json.dump(item_list,f,ensure_ascii=False)
将抓取的数据保存到本地 json文件
# 抓取数据一般处理方式
【1】txt文件
【2】csv文件
【3】json文件
【4】MySQL数据库
【5】MongoDB数据库
【6】Redis数据库
多线程爬虫
-
应用场景
【1】多进程 :CPU密集程序
【2】多线程 :爬虫(网络I/O)、本地磁盘I/O
知识点回顾
-
队列
【1】导入模块
from queue import Queue
【2】使用
q = Queue()
q.put(url)
q.get() # 当队列为空时,阻塞
q.empty() # 判断队列是否为空,True/False
【3】q.get()解除阻塞方式
3.1) q.get(block=False)
3.2) q.get(block=True,timeout=3)
3.3) if not q.empty():
q.get()
-
线程模块
# 导入模块
from threading import Thread
# 使用流程
t = Thread(target=函数名) # 创建线程对象
t.start() # 创建并启动线程
t.join() # 阻塞等待回收线程
# 如何创建多线程
t_list = []
for i in range(5):
t = Thread(target=函数名)
t_list.append(t)
t.start()
for t in t_list:
t.join()
-
线程锁
from threading import Lock
lock = Lock()
lock.acquire()
lock.release()
【注意】上锁成功后,再次上锁会阻塞
-
多线程爬虫示例代码
# 抓取豆瓣电影剧情类别下的电影信息
"""
豆瓣电影 - 剧情 - 抓取
"""
import requests
from fake_useragent import UserAgent
import time
import random
from threading import Thread,Lock
from queue import Queue
class DoubanSpider:
def __init__(self):
self.url = 'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start={}&limit=20'
self.i = 0
# 队列 + 锁
self.q = Queue()
self.lock = Lock()
def get_agent(self):
"""获取随机的User-Agent"""
return UserAgent().random
def url_in(self):
"""把所有要抓取的URL地址入队列"""
for start in range(0,684,20):
url = self.url.format(start)
# url入队列
self.q.put(url)
# 线程事件函数:请求+解析+数据处理
def get_html(self):
while True:
# 从队列中获取URL地址
# 一定要在判断队列是否为空 和 get() 地址 前后加锁,防止队列中只剩一个地址时出现重复判断
self.lock.acquire()
if not self.q.empty():
headers = {'User-Agent': self.get_agent()}
url = self.q.get()
self.lock.release()
html = requests.get(url=url, headers=headers).json()
self.parse_html(html)
else:
# 如果队列为空,则最终必须释放锁
self.lock.release()
break
def parse_html(self, html):
"""解析"""
# html: [{},{},{},{}]
item = {}
for one_film in html:
item['rank'] = one_film['rank']
item['title'] = one_film['title']
item['score'] = one_film['score']
print(item)
# 加锁 + 释放锁
self.lock.acquire()
self.i += 1
self.lock.release()
def run(self):
# 先让URL地址入队列
self.url_in()
# 创建多个线程,开干吧
t_list = []
for i in range(1):
t = Thread(target=self.get_html)
t_list.append(t)
t.start()
for t in t_list:
t.join()
print('数量:',self.i)
if __name__ == '__main__':
start_time = time.time()
spider = DoubanSpider()
spider.run()
end_time = time.time()
print('执行时间:%.2f' % (end_time-start_time))
selenium
selenium+PhantomJS/Chrome/Firefox
-
selenium
【1】定义
1.1) 开源的Web自动化测试工具
【2】用途
2.1) 对Web系统进行功能性测试,版本迭代时避免重复劳动
2.2) 兼容性测试(测试web程序在不同操作系统和不同浏览器中是否运行正常)
2.3) 对web系统进行大数量测试
【3】特点
3.1) 可根据指令操控浏览器
3.2) 只是工具,必须与第三方浏览器结合使用
【4】安装
4.1) Linux: pip3 install selenium
4.2) Windows: python -m pip install selenium
-
PhantomJS浏览器
【1】定义
phantomjs为无界面浏览器(又称无头浏览器),在内存中进行页面加载,高效
【2】下载地址
2.1) chromedriver : 下载对应版本
http://npm.taobao.org/mirrors/chromedriver/
2.2) geckodriver
https://github.com/mozilla/geckodriver/releases
2.3) phantomjs
https://phantomjs.org/download.html
【3】linux安装
3.1) 下载后解压 : tar -zxvf geckodriver.tar.gz
3.2) 拷贝解压后文件到 /usr/bin/ (添加环境变量)
cp geckodriver /usr/bin/
3.3) 添加可执行权限
chmod 777 /usr/bin/geckodriver
【4】Windows系统安装
4.1) 下载对应版本的phantomjs、chromedriver、geckodriver
4.2) 把chromedriver.exe拷贝到python安装目录的Scripts目录下(添加到系统环境变量)
# 查看python安装路径: where python
4.3) 验证
cmd命令行: chromedriver
总结
【1】解压 - 放到用户主目录(chromedriver、geckodriver、phantomjs)
【2】拷贝 - cp /home/tarena/chromedriver /usr/bin/
【3】权限 - chmod 777 /usr/bin/chromedriver
验证
【linux | Windows】
ipython3
from selenium import webdriver
webdriver.Chrome()
或者
webdriver.Firefox()
【mac】
ipython3
from selenium import webdriver
webdriver.Chrome(executable_path=’/Users/xxx/chromedriver’)
或者
webdriver.Firefox(executable_path=’/User/xxx/geckodriver’)
-
参考代码
使用 selenium+浏览器 打开百度
# 导入seleinum的webdriver接口
from selenium import webdriver
import time
# 创建浏览器对象
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')
# 5秒钟后关闭浏览器
time.sleep(5)
browser.quit()
"""示例代码二:打开百度,搜索杨幂,点击搜索,查看"""
from selenium import webdriver
import time
# 1.创建浏览器对象 - 已经打开了浏览器
browser = webdriver.Chrome()
# 2.输入: http://www.baidu.com/
browser.get('http://www.baidu.com/')
# 3.找到搜索框,向这个节点发送文字: 杨幂
browser.find_element_by_xpath('//*[@id="kw"]').send_keys('杨幂')
# 4.找到 百度一下 按钮,点击一下
browser.find_element_by_xpath('//*[@id="su"]').click()
练习
【1】肯德基餐厅门店信息抓取(POST请求练习,非多线程)
1.1) URL地址: http://www.kfc.com.cn/kfccda/storelist/index.aspx
1.2) 所抓数据: 餐厅编号、餐厅名称、餐厅地址、城市
1.3) 数据存储: 保存到本地的json文件中,kfc.json
1.4) 程序运行效果:
请输入城市名称:北京
把北京的所有肯德基门店的信息保存到json文件
多线程抓取百度图片
【1】多线程实现抓取百度图片指定关键字的所有图片
【2】URL地址:https://image.baidu.com/
【3】实现效果
请输入关键字:杨幂
则:在当前目录下创建 ./images/杨幂/ 目录,并将所有杨幂图片保存到此目录下
-
步骤
【1】输入关键字,进入图片页面,滚动鼠标滑轮发现所有图片链接均为动态加载
【2】F12抓取到返回实际数据的json地址如下:
https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord={}&cl=2&lm=-1&hd=&latest=©right=&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word={}&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&pn={}&rn=30&gsm=1e&1599728538999=
【3】分析有效的查询参数如下:
queryWord: 杨幂
word: 杨幂
pn: 30 pn的值为 0 30 60 90 120 … …
【4】图片总数如何获取?
在json数据中能够查到图片总数
displayNum: 1037274
-
代码实现
import requests
from threading import Thread,Lock
from queue import Queue
from fake_useragent import UserAgent
import os
from urllib import parse
import time
class BaiduImageSpider:
def __init__(self):
self.keyword = input('请输入关键字:')
self.directory = './images/{}/'.format(self.keyword)
if not os.path.exists(self.directory):
os.makedirs(self.directory)
# 编码
self.params = parse.quote(self.keyword)
self.url = 'https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord={}&cl=2&lm=-1&hd=&latest=©right=&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word={}&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&pn={}&rn=30&gsm=5a&1599724879800='
# 创建url队列、线程锁
self.url_queue = Queue()
self.lock = Lock()
def get_html(self, url):
"""请求功能函数"""
headers = {'User-Agent' : UserAgent().random}
html = requests.get(url=url, headers=headers).json()
return html
def get_total_image(self):
"""获取图片总数量"""
total_page_url = self.url.format(self.params, self.params, 0)
total_page_html = self.get_html(url=total_page_url)
total_page_nums = total_page_html['displayNum']
return total_page_nums
def url_in(self):
total_image_nums = self.get_total_image()
for pn in range(0, total_image_nums, 30):
page_url = self.url.format(self.params, self.params, pn)
self.url_queue.put(page_url)
def parse_html(self):
"""线程事件函数"""
while True:
# 加锁
self.lock.acquire()
if not self.url_queue.empty():
page_url = self.url_queue.get()
# 释放锁
self.lock.release()
html = self.get_html(url=page_url)
for one_image_dict in html['data']:
try:
image_url = one_image_dict['hoverURL']
self.save_image(image_url)
except Exception as e:
continue
else:
self.lock.release()
break
def save_image(self, image_url):
"""保存一张图片到本地"""
headers = {'User-Agent':UserAgent().random}
image_html = requests.get(url=image_url, headers=headers).content
# 加锁、释放锁
self.lock.acquire()
filename = self.directory + image_url[-24:]
self.lock.release()
with open(filename, 'wb') as f:
f.write(image_html)
print(filename, '下载成功')
def run(self):
# 让URL地址入队列
self.url_in()
# 创建多线程
t_list = []
for i in range(1):
t = Thread(target=self.parse_html)
t_list.append(t)
t.start()
for t in t_list:
t.join()
if __name__ == '__main__':
start_time = time.time()
spider = BaiduImageSpider()
spider.run()
end_time = time.time()
print('time:%.2f' % (end_time - start_time))
-
浏览器对象(browser)方法
【1】browser.get(url=url) - 地址栏输入url地址并确认
【2】browser.quit() - 关闭浏览器
【3】browser.close() - 关闭当前页
【4】browser.page_source - HTML结构源码
【5】browser.page_source.find('字符串')
从html源码中搜索指定字符串,没有找到返回:-1,经常用于判断是否为最后一页
【6】browser.maximize_window() - 浏览器窗口最大化
-
定位节点八种方法
【1】单元素查找(‘结果为1个节点对象’)
1.1) 【最常用】browser.find_element_by_id(‘id属性值’)
1.2) 【最常用】browser.find_element_by_name(‘name属性值’)
1.3) 【最常用】browser.find_element_by_class_name(‘class属性值’)
1.4) 【最万能】browser.find_element_by_xpath(‘xpath表达式’)
1.5) 【匹配a节点时常用】browser.find_element_by_link_text(‘链接文本’)
1.6) 【匹配a节点时常用】browser.find_element_by_partical_link_text(‘部分链接文本’)
1.7) 【最没用】browser.find_element_by_tag_name(‘标记名称’)
1.8) 【较常用】browser.find_element_by_css_selector(‘css表达式’)
【2】多元素查找(‘结果为[节点对象列表]’)
2.1) browser.find_elements_by_id(‘id属性值’)
2.2) browser.find_elements_by_name(‘name属性值’)
2.3) browser.find_elements_by_class_name(‘class属性值’)
2.4) browser.find_elements_by_xpath(‘xpath表达式’)
2.5) browser.find_elements_by_link_text(‘链接文本’)
2.6) browser.find_elements_by_partical_link_text(‘部分链接文本’)
2.7) browser.find_elements_by_tag_name(‘标记名称’)
2.8) browser.find_elements_by_css_selector(‘css表达式’)
【3】注意
当属性值中存在 空格 时,我们要使用 . 去代替空格
页面中class属性值为: btn btn-account
driver.find_element_by_class_name(‘btn.btn-account’).click()
-
对象操作
【1】文本框操作
1.1) node.send_keys(’’) - 向文本框发送内容
1.2) node.clear() - 清空文本
1.3) node.get_attribute(‘value’) - 获取文本内容
【2】按钮操作
1.1) node.click() - 点击
1.2) node.is_enabled() - 判断按钮是否可用
1.3) node.get_attribute(‘value’) - 获取按钮文本
【3】重要方法
node.get_attribute(‘属性名’) - 获取节点的属性值
chromedriver设置无界面模式
from selenium import webdriver
options = webdriver.ChromeOptions()
# 添加无界面参数
options.add_argument('--headless')
browser = webdriver.Chrome(options=options)
selenium - 鼠标操作
from selenium import webdriver
# 导入鼠标事件类
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
driver.get('http://www.baidu.com/')
# 移动到 设置,perform()是真正执行操作,必须有
element = driver.find_element_by_xpath('//*[@id="u1"]/a[8]')
ActionChains(driver).move_to_element(element).perform()
# 单击,弹出的Ajax元素,根据链接节点的文本内容查找
driver.find_element_by_link_text('高级搜索').click()
selenium切换页面
-
适用网站+应对方案
【1】适用网站类型
页面中点开链接出现新的窗口,但是浏览器对象browser还是之前页面的对象,需要切换到不同的窗口进行操作
【2】应对方案 - browser.switch_to.window()
# 获取当前所有句柄(窗口)- [handle1,handle2]
all_handles = browser.window_handles
# 切换browser到新的窗口,获取新窗口的对象
browser.switch_to.window(all_handles[1])
selenium - iframe
-
特点+方法
网页中嵌套了网页,先切换到iframe,然后再执行其他操作
【2】处理步骤
2.1) 切换到要处理的Frame
2.2) 在Frame中定位页面元素并进行操作
2.3) 返回当前处理的Frame的上一级页面或主页面
【3】常用方法
3.1) 切换到frame - browser.switch_to.frame(frame节点对象)
3.2) 返回上一级 - browser.switch_to.parent_frame()
3.3) 返回主页面 - browser.switch_to.default_content()
【4】使用说明
4.1) 方法一: 默认支持id和name属性值 : switch_to.frame(id属性值|name属性值)
4.2) 方法二:
a> 先找到frame节点 : frame_node = browser.find_element_by_xpath(‘xxxx’)
b> 在切换到frame : browser.switch_to.frame(frame_node)
-
小总结
【1】设置无界面模式
options = webdriver.ChromeOptions()
options.add_argument(’–headless’)
browser = webdriver.Chrome(excutable_path=’/home/tarena/chromedriver’,options=options)
【2】browser执行JS脚本
browser.execute_script(‘window.scrollTo(0,document.body.scrollHeight)’)
【3】键盘操作
from selenium.webdriver.common.keys import Keys
【4】鼠标操作
from selenium.webdriver import ActionChains
ActionChains(browser).move_to_element(‘node’).perform()
【5】切换句柄 - switch_to.frame(handle)
all_handles = browser.window_handles
browser.switch_to.window(all_handles[1])
# 开始进行数据抓取
browser.close()
browser.switch_to.window(all_handles[0])
【6】iframe子页面
browser.switch_to.frame(frame_node)
-
lxml中的xpath 和 selenium中的xpath的区别
【1】lxml中的xpath用法 - 推荐自己手写
div_list = p.xpath('//div[@class="abc"]/div')
item = {}
for div in div_list:
item['name'] = div.xpath('.//a/@href')[0]
item['likes'] = div.xpath('.//a/text()')[0]
【2】selenium中的xpath用法 - 推荐copy - copy xpath
div_list = browser.find_elements_by_xpath('//div[@class="abc"]/div')
item = {}
for div in div_list:
item['name'] = div.find_element_by_xpath('.//a').get_attribute('href')
item['likes'] = div.find_element_by_xpath('.//a').text
机器视觉与tesseract
-
概述
【1】作用
处理图形验证码
【2】三个重要概念 - OCR、tesseract-ocr、pytesseract
2.1) OCR
光学字符识别(Optical Character Recognition),通过扫描等光学输入方式将各种票据、报刊、书籍、文稿及其它印刷品的文字转化为图像信息,再利用文字识别技术将图像信息转化为电子文本
2.2) tesseract-ocr
OCR的一个底层识别库(不是模块,不能导入),由Google维护的开源OCR识别库
2.3) pytesseract
Python模块,可调用底层识别库,是对tesseract-ocr做的一层Python API封装
-
安装tesseract-ocr
【1】linux安装
yum -y install tesseract-ocr
【2】Windows安装
2.1) 下载安装包
2.2) 添加到环境变量(Path)
【3】测试(终端 | cmd命令行)
tesseract xxx.jpg 文件名
-
安装pytesseract
【1】安装
pip3 install pytesseract
【2】使用示例
import pytesseract
# Python图片处理库
from PIL import Image
# 创建图片对象
img = Image.open('test1.jpg')
# 图片转字符串
result = pytesseract.image_to_string(img)
print(result)
滑块缺口验证码
【1】URL地址: https://www.douban.com/
【2】先输入几次错误的密码,让登录出现滑块缺口验证,以便于我们破解
【3】模拟人的行为
3.1) 先快速滑动
3.2) 到离重点位置不远的地方开始减速
【4】详细看代码注释
先输入几次错误的密码,出现滑块缺口验证码
- 参考代码
from selenium import webdriver
# 导入鼠标事件类
from selenium.webdriver import ActionChains
import time
# 加速度函数
def get_tracks(distance):
"""
拿到移动轨迹,模仿人的滑动行为,先匀加速后匀减速
匀变速运动基本公式:
①v=v0+at
②s=v0t+½at²
"""
# 初速度
v = 0
# 单位时间为0.3s来统计轨迹,轨迹即0.3内的位移
t = 0.3
# 位置/轨迹列表,列表内的一个元素代表0.3s的位移
tracks = []
# 当前的位移
current = 0
# 到达mid值开始减速
mid = distance*4/5
while current < distance:
if current < mid:
# 加速度越小,单位时间内的位移越小,模拟的轨迹就越多越详细
a = 2
else:
a = -3
# 初速度
v0 = v
# 0.3秒内的位移
s = v0*t+0.5*a*(t**2)
# 当前的位置
current += s
# 添加到轨迹列表
tracks.append(round(s))
# 速度已经达到v,该速度作为下次的初速度
v = v0 + a*t
return tracks
# tracks: [第一个0.3秒的移动距离,第二个0.3秒的移动距离,...]
# 1、打开豆瓣官网 - 并将窗口最大化
browser = webdriver.Chrome()
browser.maximize_window()
browser.get('https://www.douban.com/')
# 2、切换到iframe子页面
login_frame = browser.find_element_by_xpath('//*[@id="anony-reg-new"]/div/div[1]/iframe')
browser.switch_to.frame(login_frame)
# 3、密码登录 + 用户名 + 密码 + 登录豆瓣
browser.find_element_by_xpath('/html/body/div[1]/div[1]/ul[1]/li[2]').click()
browser.find_element_by_xpath('//*[@id="username"]').send_keys('15110225726')
browser.find_element_by_xpath('//*[@id="password"]').send_keys('zhanshen001')
browser.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/div[5]/a').click()
time.sleep(4)
# 4、切换到新的iframe子页面 - 滑块验证
auth_frame = browser.find_element_by_xpath('//*[@id="TCaptcha"]/iframe')
browser.switch_to.frame(auth_frame)
# 5、按住开始滑动位置按钮 - 先移动180个像素
element = browser.find_element_by_xpath('//*[@id="tcaptcha_drag_button"]')
# click_and_hold(): 按住某个节点并保持
ActionChains(browser).click_and_hold(on_element=element).perform()
# move_to_element_with_offset(): 移动到距离某个元素(左上角坐标)多少距离的位置
ActionChains(browser).move_to_element_with_offset(to_element=element,xoffset=180,yoffset=0).perform()
# 6、使用加速度函数移动剩下的距离
tracks = get_tracks(28)
for track in tracks:
# move_by_offset() : 鼠标从当前位置移动到某个坐标
ActionChains(browser).move_by_offset(xoffset=track,yoffset=0).perform()
# 7、延迟释放鼠标: release()
time.sleep(0.5)
ActionChains(browser).release().perform()
Fiddler抓包工具
-
Fiddler常用菜单
【1】Inspector :查看数据包详细内容
1.1) 整体分为请求和响应两部分
【2】Inspector常用菜单
2.1) Headers :请求头信息
2.2) WebForms: POST请求Form表单数据 :<body>
GET请求查询参数: <QueryString>
2.3) Raw : 将整个请求显示为纯文本
移动端app数据抓取
- 方法1 - 手机 + Fiddler
设置方法见文件夹 - 移动端抓包配置
有道翻译手机版案例**
import requests
from lxml import etree
word = input('请输入要翻译的单词:')
post_url = 'http://m.youdao.com/translate'
post_data = {
'inputtext':word,
'type':'AUTO'
}
html = requests.post(url=post_url,data=post_data).text
parse_html = etree.HTML(html)
xpath_bds = '//ul[@id="translateResult"]/li/text()'
result = parse_html.xpath(xpath_bds)[0]
print(result)