zoukankan      html  css  js  c++  java
  • 使用webdriver+urllib爬取网页数据(模拟登陆,过验证码)

    urilib是python的标准库,当我们使用Python爬取网页数据时,往往用的是urllib模块,通过调用urllib模块的urlopen(url)方法返回网页对象,并使用read()方法获得url的html内容,然后使用BeautifulSoup抓取某个标签内容,结合正则表达式过滤。但是,用urllib.urlopen(url).read()获取的只是网页的静态html内容,很多动态数据(比如网站访问人数、当前在线人数、微博的点赞数等等)是不包含在静态html里面的,例如我要抓取这个bbs网站中点击打开链接 各个板块的当前在线人数,静态html网页是不包含的(不信你查看页面源代码试试,只有简单的一行)。像这些动态数据更多的是由JavaScript、JQuery、PHP等语言动态生成的,因此再用抓取静态html内容的方式就不合适了。

    本文将通过selenium webdriver模块的使用,以获取这些动态生成的内容,尤其是一些重要的动态数据。其实selenium模块的功能不是仅仅限于抓取网页,它是网络自动化测试的常用模块,在Ruby、Java里面都有广泛使用,Python里面虽然使用相对较少,但也是一个非常简洁高效容易上手的自动化测试模块。通过利用selenium的子模块webdriver的使用,解决抓取动态数据的问题,还可以对selenium有基本认识,为进一步学习自动化测试打下基础。

    一 环境配置

    1、下载geckodriver.exe:下载地址:https://github.com/mozilla/geckodriver/releases请根据系统版本选择下载;(如Windows 64位系统)


    2、下载解压后将getckodriver.exe复制到Firefox的安装目录下,
        如(C:Program FilesMozilla Firefox),并在环境变量Path中添加路径:C:Program FilesMozilla Firefox

    3、安装selenium

    pip install selenium

    4、beautifulsoup4的安装,Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,

    pip install beautifulsoup4
    pip install lxml
    pip install html5lib

     5、安装faker

    pip install faker

    二 如何爬取落网某一期数据信息

    落网的网址是http://www.luoo.net,我们利用火狐浏览器打开该网址,随便选择一期音乐,这里我点击的是摇滚,

    然后点击F12,选择第989期,进入该页面,我们就以爬取这一页的内容为例:

    进入之后,我们可以看到该网页主要包括以下内容:期刊编号,期刊标题,期刊封面,期刊描述,歌单。

    通过下方开发工具中的查看器,可以获取我们感兴趣数据的标签,选择器等信息。以歌单为例:

    程序代码如下:

    # -*- coding: utf-8 -*-
    """
    Created on Thu May 24 16:35:36 2018
    
    @author: zy
    """
    import os
    from bs4 import BeautifulSoup
    import random
    from faker import Factory
    import queue
    import threading
    import urllib.request as urllib  
    from selenium import webdriver
    import time
    
    
    
    #"gbk" codec can't encode character "xXX" in position XX : https://www.cnblogs.com/feng18/p/5646925.html
    #即字符编码是utf-8的字节,但是并不能转换成utf-8编码的字符串     
    
    '''
    爬取网页信息的类
    '''
    def random_proxies(proxy_ips):
        '''
        从proxy_ips中随机选取一个代理ip    
        
        args:
            proxy_ips:list 每个元素都是一个代理ip
        '''
        ip_index = random.randint(0, len(proxy_ips)-1)
        res = { 'http': proxy_ips[ip_index] }
        return res
    
    
    def fix_characters(s):
        '''
        替换掉s中的一些特殊字符 
        
        args:
            s:字符串
        '''
        for c in ['<', '>', ':', '"', '/', '\', '|', '?', '*']:
            s = s.replace(c, '')
        return s
    
    
    def get_static_url_response_html(url):    
        '''
        爬取静态页面数据:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#find
        
        reture:
            返回html代码
        '''
        fake = Factory.create()
        # 这里配置可用的代理IP,可以写多个
        proxy_ips = [
                    '183.129.151.130' 
                ]
        headers = {'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 
                'Accept-Charset':'GB2312,utf-8;q=0.7,*;q=0.7', 
                'Accept-Language':'zh-cn,zh;q=0.5', 
                'Cache-Control':'max-age=0', 
                'Connection':'keep-alive', 
                'Host':'John', 
                'Keep-Alive':'115', 
                'Referer':url, 
                'User-Agent': fake.user_agent()
                #'User-Agent': 'User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
                }
        req = urllib.Request(url=url,origin_req_host=random_proxies(proxy_ips),headers = headers)  
        #当该语句读取的返回值是bytes类型时,要将其转换成utf-8才能正常显示在python程序中
        response = urllib.urlopen(req).read()   
        #需要进行类型转换才能正常显示在python中      
        response = response.decode('utf-8')         
        return response
    
    
    def download(url,dstpath=None):
        '''
        利用urlretrieve()这个函数可以直接从互联网上下载文件保存到本地路径下
        
        args:
            url:网页文件或者图片以及其他数据路径,尽量不要下载网页,因为下载的是静态网页
            dstpath:保存全路况
        '''        
        if dstpath is None:
            dstpath = './code.jpg'
        try:
            urllib.urlretrieve(url,dstpath)                    
            print('Download from {} finish!'.format(url))
        except Exception as e:
            print('Download from {} fail!'.format(url))
            
            
    
    def get_dynamic_url_response_html(url):
        '''    
        selenium是一个用于Web应用自动化程序测试的工具,测试直接运行在浏览器中,就像真正的用户在操作一样
        selenium支持通过驱动真实浏览器(FirfoxDriver,IternetExplorerDriver,OperaDriver,ChromeDriver)
        selenium支持通过驱动无界面浏览器(HtmlUnit,PhantomJs)
        
    
        1、下载geckodriver.exe:下载地址:https://github.com/mozilla/geckodriver/releases请根据系统版本选择下载;(如Windows 64位系统)
        2、下载解压后将getckodriver.exe复制到Firefox的安装目录下,
        如(C:Program FilesMozilla Firefox),并在环境变量Path中添加路径:C:Program FilesMozilla Firefox
        
        return:
            返回html代码  获取url页面信息后关闭连接
        '''         
        browser = webdriver.Firefox(executable_path = r'D:ffgeckodriver.exe')    
        #html请求
        browser.get(url)    
        html = browser.page_source
        time.sleep(2)
        #html = browser.page_source.decode('utf-8')
        #关闭浏览器
        browser.quit() 
        return html
    
    
    class UrlSpyder(threading.Thread):
        '''
        使用Threading模块创建线程:http://www.runoob.com/python/python-multithreading.html
        使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:
        
        主要思路分成两部分:
            1.用来发起http请求分析出播放列表然后丢到队列中
            2.在队列中逐条下载文件到本地(如果需要下载文件)
            一般分析列表速度较快,下载速度比较慢,可以借助多线程同时进行下载
        '''
        def __init__(self, base_url, releate_urls, que=None):
            '''
            args:
                base_url:网址
                releate_urls:相对于网址的网页路径  list集合
                que:队列  Python的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,
                          和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
            '''
            threading.Thread.__init__(self)
            print('Start spider
    ')
            print ('=' * 50)
            
            #保存字段信息
            self.base_url = base_url
            if queue is None:
                self.queue = queue.Queue()     
            else:
                self.queue = que   
            self.releate_urls = releate_urls
    
        def run(self):
            '''
            把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
            '''
            #遍历每一个网页 并开始爬取
            for releate_url in self.releate_urls:
                self.spider(releate_url)
            print ('
    Crawl end
    
    ')
        
        def spider(self, releate_url):
            '''        
            爬取指定网页数据,并提取我们所需要的网页信息的函数
            
            args:
                releate_url:网页的相对路径            
            '''
            url = os.path.join(self.base_url,releate_url)        
            print ('Crawling: ' + url + '
    ')
    
            '''
            解析html  针对不同的网址,解析内容也不一样
            '''
            #获取指定网页的html代码
            response = get_dynamic_url_response_html(url)
            #response = getUrlRespHtml(url)        )
         
            #使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出:
            #soup = BeautifulSoup(response, 'lxml')
            soup = BeautifulSoup(response, 'html.parser')
        
            #输出网页信息
            #print(soup.prettify())
            #with open('./read.html','w',encoding='utf-8') as f:
                #两个内容是一样的 只是一个是标准缩进格式,另一个不是
                #f.write(str(soup.prettify()))
                #f.write(str(response))
            '''
            根据爬取网站不同,下面代码也会不一样
            解析该网站某一期的所有音乐信息
            主要包括:
                期刊信息 
                期刊封面 
                期刊描述 
                节目清单
            '''        
            #<span class="vol-number rounded">989</span>
            num = soup.find('span',class_='vol-number').get_text()
            
            #<span class="vol-title">曙色初动</span>  解析标题  find每次只返回第一个元素
            title = soup.find('span',class_='vol-title').get_text()
            '''
            <div class="vol-cover-wrapper" id="volCoverWrapper">
                <img src="http://img-cdn2.luoo.net/pics/vol/554f999074484.jpg!/fwfh/640x452" alt="曙色初动" class="vol-cover">
                <a href="http://www.luoo.net/vol/index/739" class="nav-prev" title="后一期" style="display: inline; opacity: 0;">&nbsp;</a><a href="http://www.luoo.net/vol/index/737" class="nav-next" title="前一期" style="display: inline; opacity: 0;">&nbsp;</a>
            </div>
            解析对应的图片背景
            '''        
            cover = soup.find('img', class_='vol-cover').get('src')
            '''
            <div class="vol-desc">
            本期音乐为史诗氛围类音乐专题。<br><br>史诗音乐的美好之处在于能够让人有无限多的宏伟想象。就像这一首首曲子,用岁月沧桑的厚重之声,铿锵有力的撞击人的内心深处,绽放出人世间的悲欢离合与决绝!<br><br>Cover From Meer Sadi    
            </div>
            解析描述文本
            '''
            desc = soup.find('div', class_='vol-desc').get_text() 
            
            '''
            <a href="javascript:;" rel="nofollow" class="trackname btn-play">01. Victory</a>
            <a href="javascript:;" rel="nofollow" class="trackname btn-play">02. Mythical Hero</a>
            
            解析歌单   find_all返回一个列表 所有元素
            '''        
            track_names = soup.find_all('a', attrs={'class': 'trackname'})
            track_count = len(track_names)
            tracks = []
            # 12期前的音乐编号1~9是1位(如:1~9),之后的都是2位 1~9会在左边垫0(如:01~09)
            for track in track_names:            
                _id = str(int(track.text[:2])) if (int(releate_url) < 12) else track.text[:2]  
                _name = fix_characters(track.text[4:])
                tracks.append({'id': _id, 'name': _name})
    
            phases = {
                'phase': num,                        # 期刊编号
                'title': title,                      # 期刊标题
                'cover': cover,                      # 期刊封面
                'desc': desc,                        # 期刊描述
                'track_count': track_count,          # 节目数
                'tracks': tracks                     # 节目清单(节目编号,节目名称)
                }   
                    
            #追加到队列
            self.queue.put(phases)
    
    
    
    if __name__ == '__main__':
        luoo_site = 'http://www.luoo.net/vol/index/'
         
        spyder_queue = queue.Queue()
    
        ## 创建新线程
        luoo = UrlSpyder(luoo_site, releate_urls=['1372','1370'], que=spyder_queue)
        luoo.setDaemon(True)
        #开启线程
        luoo.start()
    
        '''
        从队列中取数据,并进行下载
        '''
        count = 1
        while True:        
            if spyder_queue.qsize() <= 0:
                time.sleep(10)
                pass
            else:
                phases = spyder_queue.get()
                print(phases)
                #下载图片
                download(phases['cover'],'%d.jpg'%(count))
                count += 1
                

    运行结果如下:

    并爬取了如下两种图片:

     注意:我们一般使用 urllib.urlretrieve()函数下载图片,文件等,但是不要使用这个函数下载网页,因为下载下来也是静态网页。如果要保存网页html内容,我们使用selenium获取网页内容,然后写入到指定文件:

    with open(file_name, 'w',encoding='utf-8') as f:  
            f.write(self.browser.page_source) 

     三 使用selenium模拟登陆

    刚才我们爬取的网站是不需要登陆的,如果遇到一个需要先登录,才能爬取数据怎么办?幸运的是selenium提供了模拟鼠标点击,和键盘输入的功能,下面我们将会演示一个登陆微博的程序,并利用微博进行搜索我们需要的内容,并把数据保存下来。

    下面我先列入完整代码,一会一点点讲解:

    # -*- coding: utf-8 -*-
    """
    Created on Thu May 24 16:35:36 2018
    
    @author: zy
    """
    import os
    import time
    from selenium import webdriver
    #from selenium.common.exceptions import NoSuchElementException  
    from bs4 import BeautifulSoup
    
    
    class WeiboSpyder():
        '''
        python爬虫——基于selenium用火狐模拟登陆爬搜索关键词的微博:https://blog.csdn.net/u010454729/article/details/51225388
        0.安装火狐 
        1.安装selenium,可通过pip安装:pip install selenium 
        2.程序里面改三处:用户名、密码、搜过的关键词search_text 
        3.需要手动输入验证码,并且验证码大小写敏感,若是输错了,等3秒再输入。 
        4.爬下来了,用BeautifulSoup提取,并且将相同文本的放在一起,并且排序 
         
        时间:5秒一个,不可缩短,不然加载不出来下一页这个按钮,然后程序就挂了,若网速快些,延时可以短些。 在每个点击事件之前或者之后都加time.sleep(2)  
        '''  
        def __init__(self):
            #微博登录用户名,用户命名
            self.username_  = "你的用户名"  
            self.password_  = "你的密码"  
            self.href = 'http://s.weibo.com/'  
                  
            #获取火狐浏览器对象
            self.browser = webdriver.Firefox(executable_path = r'D:ffgeckodriver.exe')       
            #html请求
            self.browser.get(self.href)  
            time.sleep(2)  
                  
            #获取网页右上登陆元素 并点击
            login_btn = self.browser.find_element_by_xpath('//a[@node-type="loginBtn"]')  
            login_btn.click()  
            time.sleep(2)  
                  
            #获取选择账号登录元素 并点击  
            name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]')  
            name_login.click()  
            time.sleep(2)  
                  
            #获取输入用户名,密码元素 并输入用户名和密码
            username = self.browser.find_element_by_xpath('//input[@node-type="username"]')  
            password = self.browser.find_element_by_xpath('//input[@node-type="password"]')  
            username.clear()  
            username.send_keys(self.username_)  
            password.clear()  
            password.send_keys(self.password_)  
                  
            #获取提交登陆元素 并点击
            sub_btn = self.browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]')  
            sub_btn.click()  
            time.sleep(5)  
            
            '''
            #下面是验证码部分,如果需要验证码的化
            while True:  
                try:  
                    verify_img = browser.find_element_by_xpath('//img[@node-type="verifycode_image"]')  
                except NoSuchElementException:  
                    break  
                if verify_img:  
                    # 输入验证码  
                    verify_code = browser.find_element_by_xpath('//input[@node-type="verifycode"]')  
                    verify_code_ = input('verify_code > ')  
                    verify_code.clear()  
                    verify_code.send_keys(verify_code_)  
              
                    # 提交登陆  
                    sub_btn = browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]')  
                    sub_btn.click()  
                    time.sleep(2)  
                else:  
                    break  
            '''    
            #获取搜索栏元素
            #self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]')        
             
     
        def get_weibo_search(self,search_text,max_length = 20):  
            '''
            在网页中搜索指定信息,搜多到一页信息后,保存,然后搜索下一页信息,直至到max_length页
            默认在当前路径下生成一个  名字为 search_text(去除下划线) 的文件夹,下面存放爬取得网页
            args:
                search_text:需要搜索的内容
                max_length:最大爬取网页个数
            return:
                dst_dir:返回爬取网页所在的文件夹
            ''' 
            #将关键词送到搜索栏中,进行搜索  
            self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]')        
            self.search_form.clear() 
            self.search_form.send_keys(search_text)
            time.sleep(5)
            
            #获取搜索按钮元素 只有窗口最大化 才有搜索按钮
            self.search_btn = self.browser.find_element_by_xpath('//a[@class="searchBtn"]') 
            #点击搜索   
            self.search_btn.click()  
            #进入循环之前,让第一页先加载完全。  
            time.sleep(2)
                 
            print('Try download html for : {}'.format(search_text))       
            topics_name = search_text  
            #将名字里面的空格换位_   创建以搜索内容为名字的文件夹,保存爬取下来的网页
            topics_name = topics_name.replace(" ","_")
            os_path = os.getcwd()  
            dst_dir = os.path.join(os_path,topics_name)  
            if not os.path.isdir(dst_dir):  
                os.mkdir(dst_dir)  
            #捕获异常,有的搜索可能没有下一页 遇到错误会跳过
            try:            
                count = 1              
                while count <= max_length:  
                    '''
                    #保存网页   构建目标文件路径
                    '''
                    file_name = os.path.join(dst_dir,'{}_{}.html'.format(topics_name, count))  
                    #必须指定编码格式 
                    with open(file_name, 'w',encoding='utf-8') as f:  
                        f.write(self.browser.page_source)  
                    print('Page {}  download  finish!'.format(count))  
                  
                    time.sleep(3)
                    #获取下一页元素
                    self.next_page = self.browser.find_element_by_css_selector('a.next')  
                    #next_page = browser.find_element_by_xpath('//a[@class="page next S_txt1 S_line1"]')              
                    #有的时候需要手动按F5刷新,不然等太久依然还是出不来,程序就会挂,略脆弱。  
                    self.next_page.click()
                    count += 1  
                    #完成一轮之前,保存之前,先让其加载完,再保存   如果报错,可以通过调节时间长度去除错误
                    time.sleep(10)
            except Exception as e:
                print('Error:',e)
            return dst_dir
      
        def get_weibo_text(self,file_name):
            '''
            将html文件里面的<p></p>标签的内容提取出来  
            
            args:
                text:返回一个list  每一个元素都是p标签的内容
            args:
                file_name:html文件路径 
            '''        
            text = []  
            soup = BeautifulSoup(open(file_name,encoding='utf-8'),'lxml')          
            #获取tag为div class_="WB_cardwrap S_bg2 clearfix"的所有标签
            items = soup.find_all("div",class_="WB_cardwrap S_bg2 clearfix")  
            if not items:  
                text = []  
            #遍历每一额标签,提取为p的子标签内容
            for item in items:  
                line = item.find("p").get_text()  
                #print line  
                text.append(line)  
            return text  
          
        def get_weibo_all_page(self,path):
            '''
            文件夹下所有文件内容提取出来,然后合并起来
            
            args:
                path:list 每个元素都是一个文件路径,加入我们在新浪搜索内容为火影忍者,则在当前路径下会生成一个为火影忍者的文件夹
                    path就是这个文件夹的路径
            return:
                texts_all:返回合并之后的内容 是一个list            
            '''        
            texts_all = []  
            file_names = os.listdir(path)  
            #遍历当前文件夹下每个文件
            for file_name in file_names:              
                #将html文件里面的<p></p>标签的内容提取出来
                texts = self.get_weibo_text(os.path.join(path,file_name))  
                #遍历每一个元素
                for text in texts:  
                    text = text.replace("	","")  
                    text = text.strip("
    ")  
                    text = text.strip(" ")  
                    #若是重了,不加入到里面  
                    if text in texts_all:
                        pass  
                    else:  
                        texts_all.append(text)  
            return texts_all  
          
            
        def get_results_weibo(self,weibos_name):
            '''
            合并若干个文件夹下提取出来的微博  
            
            args:
                weibos_name:list 每一个元素都是一个搜索项提取出来的文本文件
            args:
                
            '''
            texts = []  
            for file_name in weibos_name:  
                with open(file_name,encoding='utf-8') as f:  
                    text = f.readlines()  
                    for line in text:  
                        line = line.strip("
    ")  
                        if line not in texts:  
                            texts.append(line)  
            return texts  
    
    
    
    
    if __name__ == '__main__':
        print('开始搜索')
        search = WeiboSpyder()
        
        #在新浪搜索中需要搜索内容
        searchs_text = ["火影忍者","苍井空","波多野结衣"]  
        
        #遍历要搜索的每一行
        for search_text in searchs_text:  
            path = search.get_weibo_search(search_text,max_length=5)          
            texts_all = search.get_weibo_all_page(path)  
            #文本排序
            texts_all_sorted = sorted(texts_all)  
            weibo_text_name = path + "_weibos.txt"  
            with open(weibo_text_name,"w",encoding='utf-8')  as f:
                #一行一行的写入
                for text in texts_all_sorted:  
                    f.write(text + "
    ")          
    
    
        #将几个_weibos.txt文件合并到一起  
        print("Together:")  
        file_names_weibos = [i for i in os.listdir(os.getcwd()) if i.endswith("_weibos.txt")]  
        texts = search.get_results_weibo(file_names_weibos)  
        with open("results.txt","w",encoding='utf-8')  as f:
            for text in sorted(texts):  
                f.write(text+"
    ")      
        print("Together finish!")  
        

    微博的官网是http://s.weibo.com/,同样我们点击F12,打开网页:我们先获取右上角登陆按钮的标签信息:

    我们怎样可以实现点击这个登陆的事件呢,在上面代码可以看到我定义了一个WeiboSpyder的类,其中在构造函数中负责网页的登陆功能。

    我们首先通过向指定网址发送html请求,进行加载网页:

            #获取火狐浏览器对象
            self.browser = webdriver.Firefox(executable_path = r'D:ffgeckodriver.exe')              
            #html请求
            self.browser.get(self.href)  
            time.sleep(2)  

    然后通过find_element_by_xpath()函数获取登陆标签,获取该标签之后,我们触发点击事件,为了等到网页加载出来,我们在点击时候,睡眠2s。

            #获取选择账号登录元素 并点击  
            name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]')  
            name_login.click()  
            time.sleep(2)  

    同理,后面我们获取选择账号登陆标签,然后获取登陆名,登录密码元素,然后利用send_keys()方法输入登陆信息,最后获取登陆标签,开始登陆:

            #获取选择账号登录元素 并点击  
            name_login = self.browser.find_element_by_xpath('//a[@action-data="tabname=login"]')  
            name_login.click()  
            time.sleep(2)  
                  
            #获取输入用户名,密码元素 并输入用户名和密码
            username = self.browser.find_element_by_xpath('//input[@node-type="username"]')  
            password = self.browser.find_element_by_xpath('//input[@node-type="password"]')  
            username.clear()  
            username.send_keys(self.username_)  
            password.clear()  
            password.send_keys(self.password_)  
                  
            #获取提交登陆元素 并点击
            sub_btn = self.browser.find_element_by_xpath('//a[@suda-data="key=tblog_weibologin3&value=click_sign"]')  
            sub_btn.click()  
            time.sleep(5)  

    由于这里不需要验证码登陆,我们就跳过验证码了,在下一个案例中我会教大家如何过验证码。

    完成登陆后,我们在主程序中开始利用微博的搜索引擎搜索指定内容:

    if __name__ == '__main__':
        print('开始搜索')
        search = WeiboSpyder()
        
        #在新浪搜索中需要搜索内容
        searchs_text = ["火影忍者","苍井空","波多野结衣"]  
        
        #遍历要搜索的每一行
        for search_text in searchs_text:  
            path = search.get_weibo_search(search_text,max_length=5)          
            texts_all = search.get_weibo_all_page(path)  
            #文本排序
            texts_all_sorted = sorted(texts_all)  
            weibo_text_name = path + "_weibos.txt"  
            with open(weibo_text_name,"w",encoding='utf-8')  as f:
                #一行一行的写入
                for text in texts_all_sorted:  
                    f.write(text + "
    ")          
    
    
        #将几个_weibos.txt文件合并到一起  
        print("Together:")  
        file_names_weibos = [i for i in os.listdir(os.getcwd()) if i.endswith("_weibos.txt")]  
        texts = search.get_results_weibo(file_names_weibos)  
        with open("results.txt","w",encoding='utf-8')  as f:
            for text in sorted(texts):  
                f.write(text+"
    ")      
        print("Together finish!")  

    我在这里搜索的内容主要包括:

     searchs_text = ["火影忍者","苍井空","波多野结衣"]  

    然后遍历每一项,开始调用get_weibo_search()方法:

     def get_weibo_search(self,search_text,max_length = 20):  
            '''
            在网页中搜索指定信息,搜多到一页信息后,保存,然后搜索下一页信息,直至到max_length页
            默认在当前路径下生成一个  名字为 search_text(去除下划线) 的文件夹,下面存放爬取得网页
            args:
                search_text:需要搜索的内容
                max_length:最大爬取网页个数
            return:
                dst_dir:返回爬取网页所在的文件夹
            ''' 
            #将关键词送到搜索栏中,进行搜索  
            self.search_form = self.browser.find_element_by_xpath('//input[@class="searchInp_form"]')        
            self.search_form.clear() 
            self.search_form.send_keys(search_text)
            time.sleep(5)
            
            #获取搜索按钮元素 只有窗口最大化 才有搜索按钮
            self.search_btn = self.browser.find_element_by_xpath('//a[@class="searchBtn"]') 
            #点击搜索   
            self.search_btn.click()  
            #进入循环之前,让第一页先加载完全。  
            time.sleep(2)
                 
            print('Try download html for : {}'.format(search_text))       
            topics_name = search_text  
            #将名字里面的空格换位_   创建以搜索内容为名字的文件夹,保存爬取下来的网页
            topics_name = topics_name.replace(" ","_")
            os_path = os.getcwd()  
            dst_dir = os.path.join(os_path,topics_name)  
            if not os.path.isdir(dst_dir):  
                os.mkdir(dst_dir)  
            #捕获异常,有的搜索可能没有下一页 遇到错误会跳过
            try:            
                count = 1              
                while count <= max_length:  
                    '''
                    #保存网页   构建目标文件路径
                    '''
                    file_name = os.path.join(dst_dir,'{}_{}.html'.format(topics_name, count))  
                    #必须指定编码格式 
                    with open(file_name, 'w',encoding='utf-8') as f:  
                        f.write(self.browser.page_source)  
                    print('Page {}  download  finish!'.format(count))  
                  
                    time.sleep(3)
                    #获取下一页元素
                    self.next_page = self.browser.find_element_by_css_selector('a.next')  
                    #next_page = browser.find_element_by_xpath('//a[@class="page next S_txt1 S_line1"]')              
                    #有的时候需要手动按F5刷新,不然等太久依然还是出不来,程序就会挂,略脆弱。  
                    self.next_page.click()
                    count += 1  
                    #完成一轮之前,保存之前,先让其加载完,再保存   如果报错,可以通过调节时间长度去除错误
                    time.sleep(10)
            except Exception as e:
                print('Error:',e)
            return dst_dir

    这个函数主要就做了以下事情:

    • 将关键词送到搜索栏中,进行搜索
    • 创建一个文件夹,把刚才搜索的网页保存下来
    • 获取下一页元素,并触发点击事件,然后再把新的网页保存下来
    • 重复上一步,直至保存了max_length页,或者触发的异常,停止搜索
    • 返回文件夹路径

    程序运行结果如下:

    如果我们运行出现类似以下的错误:

    这主要是因为我们页面没有加载完,所以才会找不到指定元素,我们可以通过延长睡眠时间解决该问题。

    并且我们会生成三个文件夹:

    为什么搜索苍井空和波多野结衣只有一条数据,主要是因为被屏蔽了:

    后面的代码get_weibo_all_page()函数以及get_weibo_text(),get_results_weibo()函数就是利用BeautifulSoup对刚才获取的几个网页内容进行提取,这里就不过多介绍了,最终还会生成几个文件:

    results.txt是对另外三个文件进行合并,内容如下:

     四 爬取需要验证码的网站

     其实爬取需要验证码网站和上一个案例是类似的,只不过需要我们输入验证码,如何过验证码主要有两种方式,一种是利用手动输入,还有一种是采用自动识别技术识别出验证码,验证码的识别需要对图像处理具有一定的基础,或者我们利用人工神经网络识别,但是这里为了简单,我们可以采用

    tesseract.exe这个软件来识别,不过识别准确率很低,这个软件使用之前需要准备工作

    然后我们可以安装相对应的python库,来调用该软件:

    pip install  pytesser3

     不过我使用的时候总是报错,因此就把该库下载了,并修改了原来库的代码,保存在pytesser.py

    # -*- coding: utf-8 -*-
    """
    Created on Sat May 26 09:19:09 2018
    
    @author: lenovo
    """
    import subprocess
    import os
    from PIL import Image
    
    '''
    使用之前需要准备工作
    1.下载tesseract.exe  https://www.polarxiong.com/archives/python-pytesser-tesseract.html 
      安装在D:Tesseract-OCR
    2.配置环境变量 TESSDATE PREFIX = D:Tesseract-OCR
    '''
    
    pwd = os.getcwd()
    #tesseract.exe在命令行调用的命令 或者tesseract.exe文件的全路径
    tesseract_exe_name = 'D:\Tesseract-OCR\tesseract' 
    #把图像保存成tesseract兼容格式bmp
    scratch_image_name = pwd + os.sep + "temp.bmp"
    #识别后txt文件保存的路径
    scratch_text_name_root = pwd + os.sep + "temp" 
    #识别之后是否清楚生成的临时文件
    cleanup_scratch_flag = True  
    
    
    def image_to_scratch(im, scratch_image_name):
        '''
        Saves image in memory to scratch file.  .bmp format will be read correctly by Tesseract
        保存图像到路径scratch_image_name 即把图片转换成Tesseract兼容格式 bmp
        '''
        im.save(scratch_image_name, dpi=(200,200))
    
    
    def    retrieve_text(scratch_text_name_root):
        '''
        从识别的txt文件中读取文本
        '''
        with open(scratch_text_name_root + '.txt','r',encoding='utf-8') as f:
            text = f.read()    
        return text
    
    
    def perform_cleanup(scratch_image_name, scratch_text_name_root):
        '''
        清除临时文件
        '''
        for name in (scratch_image_name, scratch_text_name_root + '.txt', "tesseract.log"):
            try:
                os.remove(name)
            except OSError:
                pass
    
    def call_tesseract(input_filename, output_filename):  
        '''
        Calls external tesseract.exe on input file (restrictions on types),
         outputting output_filename +"txt"
        调用 tesseract.exe识别图片
        '''
        print(tesseract_exe_name,input_filename,output_filename)
        args = [tesseract_exe_name, input_filename, output_filename]
        proc = subprocess.Popen(args)
        retcode = proc.wait()
        if retcode!=0:
            print('Open process error')
            
            
    def image_to_string(im, cleanup = cleanup_scratch_flag):
        """Converts im to file, applies tesseract, and fetches resulting text.
        If cleanup=True, delete scratch files after operation."""
        try:
           #保存图片为scratch_image_name
            image_to_scratch(im, scratch_image_name)
           #调用tesseract生成识别文本
            call_tesseract(scratch_image_name, scratch_text_name_root)
           # 提取文本内容
            text = retrieve_text(scratch_text_name_root)
        finally:
            if cleanup:
                perform_cleanup(scratch_image_name, scratch_text_name_root)
        return text
    
    
    def image_file_to_string(filename, cleanup = cleanup_scratch_flag, graceful_errors=True):
        """Applies tesseract to filename; or, if image is incompatible and graceful_errors=True,
        converts to compatible format and then applies tesseract.  Fetches resulting text.
        If cleanup=True, delete scratch files after operation."""
        try:
            try:
                call_tesseract(filename, scratch_text_name_root)
                text = retrieve_text(scratch_text_name_root)
            except:
                im = Image.open(filename)
                text = image_to_string(im, cleanup)
        finally:
            if cleanup:
                perform_cleanup(scratch_image_name, scratch_text_name_root)
        return text

    下面我以爬取超星网站的评论数据为例:网址http://passport2.chaoxing.com/login?fid=2218&refer=http://i.mooc.chaoxing.com

    按下F12,打开该网址,我们可以看到有一处需要输入验证码:

    同样我定义了一个chaoxingSpy类,其中有两个函数是用来处理验证码的:

     def read_code(self,file=None,auto=True):
            '''
            识别验证码
            
            args:
                file:验证码图片所在本机路径,如果为None,则自动从网页中截取验证码图片保存到'./code.jpg'
                auto:表示是否自动识别 为True:自动识别 False:手动识别  自动识别准确的不高
            '''
            '''
            对验证码进行区域截图,并保存
            '''
            if file is None:
                #裁切后的验证码路径
                file = './code.jpg'
                #对网页进行全截图 
                self.browser.get_screenshot_as_file('./all.jpg')
                img = Image.open('./all.jpg')
                #设置要裁剪的区域
                box = (490,340,570,370)
                #裁切        
                region = img.crop(box)   
                #RGBA to RGB
                region= region.convert('RGB')
                region.save(file)
                print('图片裁切成功!')                
            else:
                img = Image.open(file)
            
            if auto:
                verify_code = self.image_file_to_string(file)
            else:
                #显示图片
                region.show()
                #手动输入验证码  获取验证码输入元素        
                verify_code = input('verify_code > ')      
            return verify_code
        
    
        def image_file_to_string(self,file):
            '''
            图像增强,自动识别简单验证码
            
            args:
                file:验证码文件的路径
            return:
                code:识别的验证码
            '''
            img = Image.open(file)
            #图像加强,二值化
            imgry = img.convert('L')
            #对比度增强
            sharpness = ImageEnhance.Contrast(imgry) 
            sharp_img = sharpness.enhance(2.0)
            #保存
            sharp_img.save(file)
                   
            code = pytesser.image_file_to_string(file)
            #去除非数字
            new_code = ''
            for i in code:
                if i in '0123456789':
                    new_code += i
                
            print('识别的验证码为:',new_code)
            return new_code

    当read_code()函数的auto参数传入False时,表示手动输入验证码,即在程序运行的时候,我们利用input()函数读取我们控制台输入的验证码,然后返回验证码的值。当传入True的时候,我们通过调用tesseract.exe识别验证码,为了提高准确率,我们事先对图像进行了处理,默认下我们截取当前网页的验证码图片,然后通过对图片二值化,对比度增强处理然后在进行自动识别。

    完整代码如下:

    # -*- coding: utf-8 -*-
    """
    Created on Fri May 25 18:04:13 2018
    
    @author: zy
    """
    
    
    # -*- coding: utf-8 -*-
    """
    Created on Thu May 24 16:35:36 2018
    
    @author: zy
    """
    import time
    from selenium import webdriver
    from selenium.common.exceptions import NoSuchElementException  
    import urllib.request as urllib  
    from PIL import Image,ImageEnhance
    import pytesser
    
    '''
    webdriver使用说明:https://wenku.baidu.com/view/d64005c6af45b307e9719715.html
    '''
    
    
    class chaoxingSpy():
        def __init__(self):
            '''
            登录系统
            '''
            #登录用户名,用户命名
            self.username_  = "你的用户名"  
            self.password_  = "你的密码"  
            url = 'http://passport2.chaoxing.com/login?fid=2218&refer=http://i.mooc.chaoxing.com'
            #获取火狐浏览器对象 
            self.browser = webdriver.Firefox(executable_path = r'D:ffgeckodriver.exe')   
            #html请求
            self.browser.get(url)
            #存放登录前后的cookies
            self.cookies = {'before':'','after':''}
            self.cookies['before'] = self.browser.get_cookies()
            print('登陆前:
    ')
            for cookie in self.cookies['before']:
                print('%s -> %s' % (cookie['name'], cookie['value']))
        
            #登录系统,循环尝试登录,登录成功,就会抛异常,跳出循环继续执行
            while True:
                try:
                    #获取登录用户名元素
                    username = self.browser.find_element_by_id('unameId')
                    #获取登录密码元素
                    password = self.browser.find_element_by_id('passwordId')   
                    #输入用户名和密码
                    username.clear()  
                    username.send_keys(self.username_)  
                    password.clear()  
                    password.send_keys(self.password_)      
                    
                    #判断是否需要输入验证码
                    try:  
                         #获取验证码图片
                         verify_img = self.browser.find_element_by_id('numVerCode')
                         #获取验证码url 就是验证码产生的网页
                         #code_src= verify_img.get_attribute('src')
                    except NoSuchElementException:  
                         print('不需要输入验证码进行验证!') 
                         
                    if verify_img:  
                        #获取验证码
                        verify_code_ = self.read_code(auto=False)
                        verify_code = self.browser.find_element_by_id('numcode')
                        verify_code.clear()  
                        verify_code.send_keys(verify_code_)  
                        
                    #提交登录
                    login_btn = self.browser.find_element_by_xpath('//input[@class="zl_btn_right"]') 
                    login_btn.click()
                    time.sleep(3)         
                    #判断是否已经登录进系统
                    if url == self.browser.current_url:
                        #刷新网页
                        self.browser.refresh()
                    
                except:
                    #cookies = self.browser.get_cookies()          
                    print('登录成功!')
                    #存放登录前后的cookies
                    self.cookies['after'] = self.browser.get_cookies()
                    print('登陆后:
    ')
                    for cookie in self.cookies['after']:
                        print('%s -> %s' % (cookie['name'], cookie['value']))
                    break
                    
            #利用登陆后的cookies 跳转到需要爬取的页面
            url = 'http://mooc1.chaoxing.com/mycourse/teachercourse?moocId=93196686&clazzid=3216311&edit=true'
            self.browser.get(url)     
            time.sleep(2) 
            #点击超链接打开一个新的窗口
            moretipic_a = self.browser.find_element_by_xpath('//a[@class="moreTopic"]') 
            moretipic_a.click() 
            time.sleep(2)                 
            #0是第一个打开的窗口  1是最后一个打开的窗口 转到最后一个窗口 超链接的点击最好都要加这句代码
            #https://www.cnblogs.com/nullbaby/articles/7205247.html
            #https://www.cnblogs.com/studyddup0212/p/9030455.html
            self.browser.switch_to_window(self.browser.window_handles[1])
            time.sleep(2)  
    
            
        def get_text(self):       
            '''
            获取所有的文本
            '''
            texts = []
            count = 1
            #写文件
            with open('./out_topics.txt', 'w',encoding='utf-8') as f:
                '''
                爬取文件夹外部数据
                '''
                txts = self.get_url_txt()
                for txt in txts:
                    txt = '%s. %s'%(count,txt)
                    texts.append(txt)
                    f.write(txt)
                    count += 1
            print('外部数据写入成功!')     
            '''
            遍历每一个文件夹的数据
            '''        
            with open('./in_topics.txt', 'w',encoding='utf-8') as f:
                folder_urls = self.spyder_folder()                
                for url in folder_urls:
                    txts = self.get_url_txt(url)
                    for txt in txts:
                        txt = '%s. %s'%(count,txt)
                        texts.append(txt)
                        f.write(txt)
                        count += 1
            print('文件夹数据写入成功!')        
    
                    
        def get_url_txt(self,url=None):
            '''
            遍历一个网页中的所有数据
            
            args:
                url:网页网址 如果是None,就是从当前网页读取数据
            return:
                texts:获取所有数据  list类型 每个元素对应一行数据                
            '''
            texts = []
            if url:
                #html请求
                self.browser.get(url)
                time.sleep(2)  
            count = 1
            while True:
                try:
                    #如果有查看更多,获取查看更多的超链接  循环次数越多,等待时间越长
                    getmoretopic_a = self.browser.find_element_by_id('getMoreTopic')
                    getmoretopic_a.click()        
                    time.sleep(5)   
                    print(count,' 点击查看更多成功!')                  
                    count += 1
                    if count> 40:
                        self.browser.switch_to_window(self.browser.window_handles[1])
                        time.sleep(20)
                        break
                except:              
                    self.browser.switch_to_window(self.browser.window_handles[1])
                    time.sleep(5)
                    break
            #获取所有话题
            divlist = self.browser.find_elements_by_class_name('content1118')               
            #遍历每一个话题
            for div in divlist:
                name = div.find_element_by_class_name('name').text
                date = div.find_element_by_class_name('gray').text               
                text = div.find_element_by_class_name('stuFont').find_element_by_tag_name('span').text                
                row = '%s  %s : %s
    '%(date,name,text)        
                texts.append(row)
                #print(row)
            print('Download from {0} finish!'.format(self.browser.current_url))
            return texts
                     
        def spyder_folder(self):
            '''
            爬取所有的文件夹路径
            
            args:
                folderList:list每一个元素对应一个文件夹的网址
            '''
            ul = self.browser.find_element_by_xpath('//ul[@class="folderList"]')         
            folderList = []
            alist = ul.find_elements_by_tag_name('a')
            for a in alist:
                folderList.append(a.get_attribute('href'))
            return  folderList
            
        
        def read_code(self,file=None,auto=True):
            '''
            识别验证码
            
            args:
                file:验证码图片所在本机路径,如果为None,则自动从网页中截取验证码图片保存到'./code.jpg'
                auto:表示是否自动识别 为True:自动识别 False:手动识别  自动识别准确的不高
            '''
            '''
            对验证码进行区域截图,并保存
            '''
            if file is None:
                #裁切后的验证码路径
                file = './code.jpg'
                #对网页进行全截图 
                self.browser.get_screenshot_as_file('./all.jpg')
                img = Image.open('./all.jpg')
                #设置要裁剪的区域
                box = (490,340,570,370)
                #裁切        
                region = img.crop(box)   
                #RGBA to RGB
                region= region.convert('RGB')
                region.save(file)
                print('图片裁切成功!')                
            else:
                img = Image.open(file)
            
            if auto:
                verify_code = self.image_file_to_string(file)
            else:
                #显示图片
                region.show()
                #手动输入验证码  获取验证码输入元素        
                verify_code = input('verify_code > ')      
            return verify_code
        
    
        def image_file_to_string(self,file):
            '''
            图像增强,自动识别简单验证码
            
            args:
                file:验证码文件的路径
            return:
                code:识别的验证码
            '''
            img = Image.open(file)
            #图像加强,二值化
            imgry = img.convert('L')
            #对比度增强
            sharpness = ImageEnhance.Contrast(imgry) 
            sharp_img = sharpness.enhance(2.0)
            #保存
            sharp_img.save(file)
                   
            code = pytesser.image_file_to_string(file)
            #去除非数字
            new_code = ''
            for i in code:
                if i in '0123456789':
                    new_code += i
                
            print('识别的验证码为:',new_code)
            return new_code
            
          
        
        def download(self,url,dstpath=None):
            '''
            利用urlretrieve()这个函数可以直接从互联网上下载文件保存到本地路径下
            
            args:
                url:网页文件或者图片以及其他数据路径
                dstpath:保存全路况
            '''        
            if dstpath is None:
                dstpath = './code.jpg'
            try:
                urllib.urlretrieve(url,dstpath)                    
                print('Download from {} finish!'.format(url))
            except Exception as e:
                print('Download from {} fail!'.format(url))
                
            
    
    if __name__ == '__main__':
        #加载页面
        spy = chaoxingSpy()
        spy.get_text()
     

    我们主要是爬取下面网页中的评论人时间,评论人姓名,评论人内容:

    注意:在程序运行时如点击下面的查看更多话题,会创建一个新的窗口:

    而我们需要获取的数据保存在讨论列表页面的话,这时候我们需要切换窗口,代码如下:

            #点击超链接打开一个新的窗口
            moretipic_a = self.browser.find_element_by_xpath('//a[@class="moreTopic"]') 
            moretipic_a.click() 
            time.sleep(2)                 
            #0是第一个打开的窗口  1是最后一个打开的窗口 转到最后一个窗口 超链接的点击最好都要加这句代码
            #https://www.cnblogs.com/nullbaby/articles/7205247.html
            #https://www.cnblogs.com/studyddup0212/p/9030455.html
            self.browser.switch_to_window(self.browser.window_handles[1])
            time.sleep(2)  

    程序运行后,会生成另个文件out_topics.txt和in_topics.txt,下面我们显示其中一个文件的的内容:


     

    为了避免我们每次都要进行登录,我们可以利用cookies绕过登录,详细内容可以搜索其他博客。

    参考文献

    [1]Beautiful Soup 4.2.0 文档

    [2]python爬虫——基于selenium用火狐模拟登陆爬搜索关键词的微博

    [3]webdriver使用说明

    [4]selenium - switch_to.window() - 多窗口切换

    [5]Python 爬虫入门(四)—— 验证码上篇(主要讲述验证码验证流程,不含破解验证码)

    [6]Python3.6+selenium+pytesser3 实现爬虫:含验证码和弹框的页面信息爬取

    [7]使用Threading模块创建线程

  • 相关阅读:
    AptitudeSystem 2.0
    angularjs开发常见问题-2(angularjs内置过滤器)
    经常使用 Java API
    Spring Boot JPA 连接数据库
    机房收费系统个人重构版:软工文档中那些图
    Android
    Spring Boot 动态数据源(多数据源自己主动切换)
    java的nio包的SelectionKey,Selector,SelectableChannel三者的缠绵关系概述
    初探linux子系统集之timer子系统(三)
    mobiscroll 案例git
  • 原文地址:https://www.cnblogs.com/zyly/p/9096217.html
Copyright © 2011-2022 走看看