使用webdriver+urllib爬取网页数据

Posted 大奥特曼打小怪兽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用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 Files\\Mozilla Firefox),并在环境变量Path中添加路径:C:\\Program Files\\Mozilla 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 Files\\Mozilla Firefox),并在环境变量Path中添加路径:C:\\Program Files\\Mozilla Firefox
    
    return:
        返回html代码  获取url页面信息后关闭连接
    \'\'\'         
    browser = webdriver.Firefox(executable_path = r\'D:\\ff\\geckodriver.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\\n\')
        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 (\'\\nCrawl end\\n\\n\')
    
    def spider(self, releate_url):
        \'\'\'        
        爬取指定网页数据,并提取我们所需要的网页信息的函数
        
        args:
            releate_url:网页的相对路径            
        \'\'\'
        url = os.path.join(self.base_url,releate_url)        
        print (\'Crawling: \' + url + \'\\n\')

        \'\'\'
        解析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:\\ff\\geckodriver.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("\\t","")  
                text = text.strip("\\n")  
                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("\\n")  
                    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 + "\\n")          


    #将几个_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","wscrapy中使用selenium+webdriver获取网页源码,爬取简书网站

python爬虫01-使用urllib爬取网页

爬取网页数据

python爬取网页数据方法

python 爬取网页出错 菜鸟求解

urllib2和webdriver得到的网页的源代码的不同