爬虫Rquests模块与selenium使用

requests模块

常用方法

  • 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地址

请求头 User-Agent

# 请求头(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)
    

爬虫编码模块

  • urllib.parse模块
    1、标准库模块:urllib.parse
    2、导入方式:
    import urllib.parse
    from urllib import parse
    作用:
    给URL地址中查询参数进行编码

函数使用方法

查询参数编码 urlencode({ 参数为字典 })

给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)
    【3format()方法
     '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)
    

quote(‘参数为字符串’)使用

  • 使用方法

    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)
    

模块re(正则解析)

使用流程

# 方法一 
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=02页:https://maoyan.com/board/4?offset=10
        第n页:offset=(n-1)*103】编写正则表达式
        <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.702.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(查询参数)
    

豆瓣电影数据抓取案例

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/False3】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)

你可能感兴趣的:(python,DevOps,Linux,爬虫,python,运维,selenium)