python爬虫学习(六)

python爬虫学习(五)回顾

  • Ajax动态加载数据抓取流程

    1】F12打开控制台,执行页面动作抓取网络数据包
    
    【2】抓取json文件URL地址
       2.1) 控制台中 XHR :找到异步加载的数据包
       2.2) GET请求: Network -> XHR -> URL 和 Query String Parameters(查询参数)
       2.3) POST请求:Network -> XHR -> URL 和 Form Data
    
  • json模块

    1】抓取的json数据转为python数据类型
        1.1) html = json.loads('[{},{},{}]')
        1.2) html = requests.get(url=url,headers=headers).json()
        1.3) html = requests.post(url=url,data=data,headers=headers).json()2】抓取数据保存到json文件
        import json
        with open('xxx.json','w') as f:
            json.dump([{},{},{}],f,ensure_ascii=False)
    
  • 数据抓取最终梳理

    1】响应内容中存在
       1.1) 确认抓取数据在响应内容中是否存在
        
       1.2) 分析页面结构,观察URL地址规律
            a) 大体查看响应内容结构,查看是否有更改 -- (百度视频案例)
            b) 查看页面跳转时URL地址变化,查看是否新跳转 -- (民政部案例)
            
       1.3) 开始码代码进行数据抓取
    
    【2】响应内容中不存在
       2.1) 确认抓取数据在响应内容中是否存在
        
       2.2) F12抓包,开始刷新页面或执行某些行为,主要查看XHR异步加载数据包
            a) GET请求: Request URL、Request Headers、Query String Paramters
            b) POST请求:Request URL、Request Headers、FormData
                
       2.3) 观察查询参数或者Form表单数据规律,如果需要进行进一步抓包分析处理
            a) 比如有道翻译的 salt+sign,抓取并分析JS做进一步处理
            b) 此处注意请求头中的cookie和referer以及User-Agent
            
       2.4) 使用res.json()获取数据,利用列表或者字典的方法获取所需数据
    
  • 多线程爬虫思路梳理

    1】所用到的模块
        1.1) from threading import Thread
        1.2) from threading import Lock
        1.3) from queue import Queue
    
    【2】整体思路
        2.1) 创建URL队列: q = Queue()
        2.2) 产生URL地址,放入队列: q.put(url)
        2.3) 线程事件函数: 从队列中获取地址,开始抓取: url = q.get()
        2.4) 创建多线程,并运行
        
    【3】代码结构
        def __init__(self):
            """创建URL队列"""
            self.q = Queue()
            
        def url_in(self):
            """生成待爬取的URL地址,入队列"""
            pass
        
        def parse_html(self):
            """线程事件函数,获取地址,进行数据抓取"""
            while True:
                if not self.q.empty():
                    url = self.q.get()
                else:
                    break
                    
        def run(self):
            self.url_in()
            t_list = []
            for i in range(3):
                t = Thread(target=self.parse_html)
                t_list.append(t)
                t.start()
                
            for th in t_list:
                th.join()
    

python爬虫学习(六)

多线程爬虫

  • 多线程回顾

    1】应用场景
        1.1) 多进程 :CPU密集程序
        1.2) 多线程 :爬虫(网络I/O)、本地磁盘I/O
        
    【2】队列要点: q.get()防止阻塞方式
        2.1) 方法1: q.get(block=False)
        2.2) 方法2: q.get(block=True,timeout=3)
        2.3) 方法3:
            if not q.empty():
               q.get()3】线程锁的使用: 
        from threading import Lock
        lock = Lock()
        lock.acquire()
        lock.release()
       【注意】: 上锁后再次上锁会阻塞,多线程操作共享资源时需要加锁和释放锁
    

小米应用商店抓取(多线程)

  • 目标

    1】网址 :百度搜 - 小米应用商店,进入官网 http://app.mi.com/2】目标 :抓取聊天社交分类下的
        2.1) 应用名称
        2.2) 应用链接
    
  • 实现步骤

    1】确认是否为动态加载
        1.1) 页面局部刷新
        1.2) 右键查看网页源代码,搜索关键字未搜到,为动态加载,需要抓取网络数据包分析
    
    【2】 F12抓取网络数据包
        2.1) 抓取返回json数据的URL地址(Headers中的Request URL)
             http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30
                
        2.2) 查看并分析查询参数(headers中的Query String Parameters)
             page: 1        只有page在变,0 1 2 3 ... ... 
             categoryId: 2
             pageSize: 303】将抓取数据保存到csv文件 - 注意线程锁问题
        from threading import Lock
        lock = Lock()
        # 加锁 + 释放锁
        lock.acquire()
        lock.release()
    
  • 整体思路

    1】在 __init__(self) 中创建文件对象,多线程操作此对象进行文件写入
        self.f = open('xiaomi.csv','a')
        self.writer = csv.writer(self.f)
        self.lock = Lock()2】每个线程抓取1页数据后将数据进行文件写入,写入文件时需要加锁
        def parse_html(self):
            app_list = []
            for xxx in xxx:
                app_list.append([name,link,typ])
            self.lock.acquire()
            self.wirter.writerows(app_list)
            self.lock.release()3】所有数据抓取完成关闭文件
        def run(self):
            self.f.close()
    
  • 代码实现

    class XiaomiSpider:
        def __init__(self):
            self.url = 'http://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
            self.headers = { 'User-Agent':UserAgent().random }
            self.q = Queue()
            self.lock = Lock()
            # 计数
            self.i = 0
            # 存入csv文件
            self.f = open('xiaomi.csv','w',encoding='utf-8')
            self.writer = csv.writer(self.f)
    
        # 获取html
        def get_html(self,url):
            html = requests.get(url=url,headers=self.headers).text
    
            return html
    
        # URL入队列
        def url_in(self):
            for page in range(67):
                url = self.url.format(page)
                self.q.put(url)
    
        # 线程事件函数
        def parse_html(self):
            while True:
                # 加锁 - 防止出现死锁(self.q中剩余1个地址,但是被多个线程判断的情况)
                self.lock.acquire()
                if not self.q.empty():
                    url = self.q.get()
                    print(url)
                    # 获取地址成功后马上释放锁,给其他线程机会,安全前提下提升效率
                    self.lock.release()
                    app_list = []
                    # 请求 + 解析  html: {'count':2000,'data':[{},{},{}]}
                    html = self.get_html(url=url)
                    print(html)
                    html = json.loads(html)
                    item = {}
                    for one_app in html['data']:
                        item['app_name'] = one_app['displayName']
                        item['app_type'] = one_app['level1CategoryName']
                        item['app_link'] = one_app['packageName']
                        print(item)
                        # 一页的app信息
                        app_list.append( (item['app_name'],item['app_type'],item['app_link']) )
                        # 加锁+释放锁
                        self.lock.acquire()
                        self.i += 1
                        self.lock.release()
                    # 把1页的数据写入到csv文件
                    self.lock.acquire()
                    self.writer.writerows(app_list)
                    self.lock.release()
                    # 简单控制一下数据抓取频率,因为我们没有代理IP,容易被封掉IP
                    time.sleep(random.uniform(2,4))
                else:
                    # 如果队列为空了,上面已经上锁,所以此处释放锁
                    self.lock.release()
                    break
    
        # 入口函数
        def run(self):
            # 1.先让URL地址入队列
            self.url_in()
            # 2.多线程,开始执行
            t_list = []
            for i in range(2):
                t = Thread(target=self.parse_html)
                t_list.append(t)
                t.start()
    
            for j in t_list:
                j.join()
    
            print('数量:',self.i)
            # 最终关闭文件
            self.f.close()
    
    if __name__ == '__main__':
        start_time = time.time()
        spider = XiaomiSpider()
        spider.run()
        end_time = time.time()
        print('执行时间:%.2f' % (end_time-start_time))
    

腾讯招聘数据抓取(多线程)

  • 确定URL地址及目标

    1】URL: 百度搜索腾讯招聘 - 查看工作岗位
    【2】目标:抓取职位的如下信息
           a> 职位名称
           b> 职位地址
           c> 职位类别(技术类、销售类...)
           d> 发布时间
           e> 工作职责
           f> 工作要求
    
  • 要求与分析

    1】通过查看网页源码,得知所需数据均为动态加载
    【2】通过F12抓取网络数据包,进行分析
    【3】一级页面抓取数据: postid
    【4】二级页面抓取数据: 名称+地址+类别+时间+职责+要求
    
  • 一级页面json地址

    """index在变,timestamp未检查"""
    https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword={}&pageIndex={}&pageSize=10&language=zh-cn&area=cn
    
  • 二级页面地址

    """postId在变,在一级页面中可拿到"""
    https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn
    
  • 多线程编写思路提示

    【思考】 两级页面是否需要指定两个队列分别存放?
            提示1: 建立2个队列,分别存放不同级的URL地址
            提示2: 从对列中get地址,最好使用timeout参数
    
  • 代码实现

    import requests
    import json
    import time
    from fake_useragent import UserAgent
    from queue import Queue
    from threading import Thread,Lock
    from urllib import parse
    import csv
    
    class TencentSpider(object):
        def __init__(self):
            self.one_url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1563912271089&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword={}&pageIndex={}&pageSize=10&language=zh-cn&area=cn'
            self.two_url = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1563912374645&postId={}&language=zh-cn'
            self.one_q = Queue()
            self.two_q = Queue()
            self.lock1 = Lock()
            self.lock2 = Lock()
            self.i = 0
            self.f = open('tencent.csv','w',encoding='utf-8')
            self.writer = csv.writer(self.f)
            # 存放所有数据的大列表,用于writerows()方法
            self.item_list = []
    
    
        def get_html(self,url):
            headers = { 'User-Agent':UserAgent().random }
            html = requests.get(url=url,headers=headers).text
            return html
    
        def url_in(self):
            keyword = input('请输入职位类别:')
            keyword = parse.quote(keyword)
            total = self.get_total(keyword)
            for page in range(1, total+1):
                one_url = self.one_url.format(keyword,page)
                print(one_url)
                self.one_q.put(one_url)
    
        # 获取总页数
        def get_total(self,keyword):
            url = self.one_url.format(keyword,1)
            html = requests.get(url=url,headers={'User-Agent':UserAgent().random}).json()
            n = int(html['Data']['Count'])
            total = n//10 if n%10==0 else n//10+1
    
            return total
    
        # 线程1事件函数
        def parse_one_page(self):
            while True:
                self.lock1.acquire()
                if not self.one_q.empty():
                    one_url = self.one_q.get()
                    self.lock1.release()
                    html = json.loads(self.get_html(one_url))
                    for job in html['Data']['Posts']:
                        post_id = job['PostId']
                        two_url = self.two_url.format(post_id)
                        self.lock1.acquire()
                        self.two_q.put(two_url)
                        self.lock1.release()
                else:
                    self.lock1.release()
                    break
    
        # 线程2事件函数
        def parse_two_page(self):
            while True:
                try:
                    self.lock2.acquire()
                    two_url = self.two_q.get(block=True,timeout=3)
                    self.lock2.release()
                    html = json.loads(self.get_html(two_url))
                    # 名称+地址+类别+时间+职责+要求
                    item = {}
                    item['name'] = html['Data']['RecruitPostName']
                    item['address'] = html['Data']['LocationName']
                    item['type'] = html['Data']['CategoryName']
                    item['time'] = html['Data']['LastUpdateTime']
                    item['duty'] = html['Data']['Responsibility']
                    item['require'] = html['Data']['Requirement']
                    # 写入csv文件数据类型: [(),(),(),...,()]
                    item_tuple = (item['name'],item['duty'],item['require'])
                    self.item_list.append(item_tuple)
    
                    print(item)
                    self.lock2.acquire()
                    self.i += 1
                    self.lock2.release()
                except Exception as e:
                    self.lock2.release()
                    print(e,end="")
                    break
    
        def run(self):
            self.url_in()
            t1_list = []
            t2_list = []
            for i in range(5):
                t = Thread(target=self.parse_one_page)
                t1_list.append(t)
                t.start()
    
            for i in range(5):
                t = Thread(target=self.parse_two_page)
                t2_list.append(t)
                t.start()
    
            for t in t1_list:
                t.join()
    
            for t in t2_list:
                t.join()
    
            print('数量:',self.i)
            # 将所有数据一次性写入文件
            self.writer.writerows(self.item_list)
            self.f.close()
    
    if __name__ == '__main__':
        start_time = time.time()
        spider = TencentSpider()
        spider.run()
        end_time = time.time()
        print('执行时间:%.2f' % (end_time-start_time))
    

cookie模拟登录

1】适用网站及场景 : 抓取需要登录才能访问的页面

豆瓣网登录案例

  • 方法一 - 登录网站手动抓取Cookie

    1】先登录成功1,获取到携带登录信息的Cookie
        登录成功 - 我的豆瓣 - F12抓包 - 刷新主页 - 找到主页的包(一般为第1个网络数据包)2】headers中携带着Cookie发请求
        headers = {
            'Cookie':'',
            'User-Agent':''
        }
    
    """方法一代码实现"""
    
    # 1、将url改为 个人主页的URL地址
    # 2、将Cookie的值改为 登录成功的Cookie值
    import requests
    
    def login():
        url = '个人主页的URL地址'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
            'Cookie':'自己抓到的Cookie值',
        }
        html = requests.get(url=url,headers=headers).text
        # 查看html中是否包含个人主页的信息 - 比如搜索 "个人主页"
        print(html)
    
    login()
    
  • 方法二

    1】原理
        1.1) 把抓取到的cookie处理为字典
        1.2) 使用requests.get()中的参数:cookies - 格式为字典
      
    【2】处理cookie为字典
        cookies = {}
        cookies_str = 'xxxx'
        for kv in cookies_str.split('; ')
            key = kv.split('=')[0]
            value = kv.split('=')[1]
            cookies[key] = value
    
    """方法二代码实现"""
    
    import requests
    
    def login():
        url = '自己账号的个人主页'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
        }
        # 处理cookie为字典
        cookies_str = '自己抓到的Cookie'
        cookies = {}
        for kv in cookies_str.split('; '):
            key = kv.split('=')[0]
            value = kv.split('=')[1]
            cookies[key] = value
    
        # 确认html
        html = requests.get(url=url,headers=headers,cookies=cookies).text
        print(html)
    
    login()
    
  • 方法三 - requests模块处理Cookie

    1】 思路 : requests模块提供了session类,来实现客户端和服务端的会话保持
    
    【2】原理
        2.1) 实例化session对象 : s = requests.session()
        2.2) 让session对象发送get或者post请求
           res = s.post(url=url,data=data,headers=headers)
           res = s.get(url=url,headers=headers)3】思路梳理
        3.1) 浏览器原理: 访问需要登录的页面会带着之前登录过的cookie
        3.2) 程序原理: 同样带着之前登录的cookie去访问 - 由session对象完成
        3.3) 具体步骤
            a> 实例化session对象
            b> 登录网站: 由session对象发送请求,登录对应网站
            c> 访问页面: 由session对象请求需要登录才能访问的页面
                
    【4】如何把用户名和密码信息提交给服务器
        4.1) 输入用户名和错误密码,登录1次进行抓包
        4.2) 在网络数据包中找到具体提交用户名和密码信息的地址,一般为POST请求
        4.3) 将正确的用户名和密码信息POST到网络数据包的URL地址 - Request URL
        
    

【5】所抓数据包信息
5.1) POST_URL: https://accounts.douban.com/j/mobile/login/basic
5.2) Form Data:
ck:
name: 13603263409
password: 111
remember: false
ticket:


```python
"""方法三代码实现"""
import requests

session = requests.session()

def login():
    post_url = 'https://accounts.douban.com/j/mobile/login/basic'
    post_data = {
        'ck':'',
        'name': '自己的用户名',
        'password': '自己的密码',
        'remember': 'false',
        'ticket': '',
    }
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36'}
    session.post(url=post_url,data=post_data,headers=headers)
    url = '自己个人主页的URL地址'
    html = session.get(url=url,headers=headers).text
    print(html)

login()
  • 模拟登录小结

    1】方法1
        先登录成功1,抓取到Cookie,定义到 headers={'Cookie':''}2】方法2
        先登录成功1,抓取到Cookie,并处理成字典,作为requests.get()中的cookies参数值
        html = requests.get(url=url,headers=headers,cookies=cookies).text
        
    【3】方法3
        s = requests.session()
        3.1) 登录失败1次抓包,找到 POST 的地址和Form表单数据
        3.2) 先POST登录: s.post(url=post_url,data=data,headers=headers)
        3.3) 再GET抓取:  html = s.get(url=get_url,headers=headers).text
    

selenium+phantomjs/Chrome/Firefox

  • selenium

    1】定义
        1.1) Web自动化测试工具,可运行在浏览器,根据指令操作浏览器
        1.2) 只是工具,必须与第三方浏览器结合使用
        
    【2】安装
        2.1) Linux: sudo pip3 install selenium
        2.2) Windows: python -m pip install selenium
    
  • phantomjs浏览器

    1】定义
        phantomjs为无界面浏览器(又称无头浏览器),在内存中进行页面加载,高效
     
    【2】下载地址
        2.1) chromedriver : 下载对应版本
             http://chromedriver.storage.googleapis.com/index.html
        2.2) geckodriver
             https://github.com/mozilla/geckodriver/releases
        2.3) phantomjs
             https://phantomjs.org/download.html
    
    【3】Ubuntu安装
        3.1) 下载后解压 : tar -zxvf geckodriver.tar.gz 
            
        3.2) 拷贝解压后文件到 /usr/bin/ (添加环境变量)
             sudo cp geckodriver /usr/bin/
            
        3.3) 添加可执行权限
             sudo 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
    
  • 示例代码

    """示例代码一:使用 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()


* **浏览器对象(browser)方法**

```python
【1】browser = webdriver.Chrome(executable_path='path')
【2】browser.get(url)
【3】browser.page_source # HTML结构源码
【4】browser.page_source.find('字符串')
     # 从html源码中搜索指定字符串,没有找到返回:-1,经常用于判断是否为最后一页
【5】browser.close() # 关闭当前页
【6】browser.quit() # 关闭浏览器
  • 定位节点

    1】单元素查找('结果为1个节点对象')
        1.1) browser.find_element_by_id('')
        1.2) browser.find_element_by_name('')
        1.3) browser.find_element_by_class_name('')
        1.4) browser.find_element_by_xpath('')
        1.5) browser.find_element_by_link_text('')
        ... ...2】多元素查找('结果为[节点对象列表]')
        2.1) browser.find_elements_by_id('')
        2.2) browser.find_elements_by_name('')
        2.3) browser.find_elements_by_class_name('')
        2.4) browser.find_elements_by_xpath('')
        ... ...
    
  • 猫眼电影示例

    from selenium import webdriver
    import time
    
    url = 'https://maoyan.com/board/4'
    browser = webdriver.Chrome()
    browser.get(url)
    
    def get_data():
        # 基准xpath: [<selenium xxx li at xxx>,<selenium xxx li at>]
        li_list = browser.find_elements_by_xpath('//*[@id="app"]/div/div/div[1]/dl/dd')
        for li in li_list:
            item = {}
            # info_list: ['1', '霸王别姬', '主演:张国荣', '上映时间:1993-01-01', '9.5']
            info_list = li.text.split('\n')
            item['number'] = info_list[0]
            item['name'] = info_list[1]
            item['star'] = info_list[2]
            item['time'] = info_list[3]
            item['score'] = info_list[4]
    
            print(item)
    
    while True:
        get_data()
        try:
            browser.find_element_by_link_text('下一页').click()
            time.sleep(2)
        except Exception as e:
            print('恭喜你!抓取结束')
            browser.quit()
            break
    
  • 节点对象操作

    1】ele.send_keys('') # 搜索框发送内容2】ele.click()3】ele.text # 获取文本内容,包含子节点和后代节点的文本内容4】ele.get_attribute('src') # 获取属性值
    

作业 - 京东爬虫(七里面有的答案哦)

  • 目标

    1】目标网址 :https://www.jd.com/2】抓取目标 :商品名称、商品价格、评价数量、商品商家
    
  • 思路提醒

    1】打开京东,到商品搜索页
    【2】匹配所有商品节点对象列表
    【3】把节点对象的文本内容取出来,查看规律,是否有更好的处理办法?
    【4】提取完1页后,判断如果不是最后1页,则点击下一页
        # 如何判断是否为最后1页???
    
  • 实现步骤-参考与提示

    # 1. 找节点
    1、首页搜索框 : //*[@id="key"]
    2、首页搜索按钮   ://*[@id="search"]/div/div[2]/button
    3、商品页的 商品信息节点对象列表 ://*[@id="J_goodsList"]/ul/li
    4for循环遍历后
      名称: .//div[@class="p-name"]/a/em
      价格: .//div[@class="p-price"]
      评论: .//div[@class="p-commit"]/strong
      商家: .//div[@class="p-shopnum"]
        
    # 2. 执行JS脚本,获取动态加载数据
    browser.execute_script(
      'window.scrollTo(0,document.body.scrollHeight)'
    )
    
  • 代码实现

    browser.excute_script(
    'window.scrollTo(0,document.body.scrollHeight)'
    

)
time.sleep(2)

【1】 搜索内容: 爬虫书
li_list = [,,…]
for li in li_list:
方法1: print(li.text)
方法2: item[‘name’]=li.find_element_by_xpath(’’)

【2】一定要注意给页面元素加载预留时间

【3】执行JS脚本


## **思考**

python
【1】多线程改写 - 链家二手房案例
【2】多线程改写 - 汽车之家案例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大枫free

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值