Python编程-让繁琐的工作自动化(十一)从Web爬取信息

 

目录

1.webbrowser模块

1.1 弄清楚URL

1.2 处理命令行参数

2. 用requests模块从Web下载文件

2.1 用requests.get()函数下载一个网页

2.2 检查错误

2.3 将下载的文件保存到硬盘

3. 用BeautifulSoup模块解析HTML

3.1 从HTML创建一个BeautifulSoup对象

4. 小项目《1》:查找一个话题并针对每个结果打开浏览器界面

5. 小项目《2》下载所有XKCD漫画

6. 自动化网页任务——selenium模块

6.1 驱动Chrome浏览器

6.2 在Web页面中寻找元素

6.3 点击页面

6.4 填写并提交表单(模拟自动登录QQ空间)

6.5 发送特殊键

6.6 点击浏览器按钮

关于selenium 的更多信息

小结


“Web抓取”是一个术语,即利用程序下载并处理来自Web的内容。例如,Google运行了许多Web抓取程序,对网页进行搜索,实现它的搜索引擎。在本章中,将学习几个模块,让在Python中抓取网页变得很容易。

webbrowser:是Python自带的,打开浏览器获取指定页面(一般会打开你Windows电脑的默认浏览器)。

requests:从因特网上下载文件和网页。

Beautiful Soup:解析HTML,即网页编写的格式。

selenium:启动并控制一个Web浏览器,selenium能够填写表单,并模拟鼠标在这个浏览器中点击。

1.webbrowser模块

webbrowser模块的open()功能可以启动一个新的浏览器到指定的URL。在交互式shell中输入以下内容:

>>> import webbrowser 
>>> webbrowser.open('http://inventwithpython.com/')

Web浏览器选项卡将打开URL http://inventwithpython.com/。这是webbrowser模块唯一可以做的事情。即便如此,该open()功能确实使一些有趣的事情成为可能。例如,将街道地址复制到剪贴板并在Google地图上显示它的地图是很繁琐的。通过编写一个简单的脚本,您可以使用剪贴板的内容在浏览器中自动启动地图,从而完成此任务。这样,您只需将地址复制到剪贴板并运行脚本,即可为您加载地图。

这是你的程序所做的:

  • 从命令行参数或剪贴板获取街道地址。

  • 打开Web浏览器,访问Google地图页面以获取地址。

这意味着您的代码需要执行以下操作:

  • 从中读取命令行参数sys.argv

  • 阅读剪贴板内容。

  • 调用该webbrowser.open()函数以打开Web浏览器。

打开一个新的文件编辑器窗口并将其另存为mapIt.py

1.1 弄清楚URL

首先你需要弄清楚,对于指定的街道,要使用怎样的URL。在浏览器中打开谷歌地图中国官网的地址:http://www.google.cn/maps ,查找一个地址,例如“杭州市”,地址栏中的URL看起来像这样:http://www.google.cn/maps/place/浙江省杭州市/@30.2610923,119.8917005,10z/data=!3m1!4b1!4m5!3m4!1s0x344bb629439aaa99:0xa7bfd183824de83a!8m2!3d30.274084!4d120.15507

地址就在URL中,但还有许多的附加文本。网站常常在URL中添加额外的数据,帮助追踪者访问或定制网站。但如果你尝试使用http://www.google.cn/maps/place/浙江省杭州市,会返现仍然可以代打正确的页面。所以你的程序可以设置为打开一个浏览器,访问http://www.google.cn/maps/place/your_address ,其中 your_address是想查看地图的地址。

1.2 处理命令行参数

在程序的#!行之后,您需要导入webbrowser模块以启动浏览器并导入sys模块以读取潜在的命令行参数。该sys.argv变量存储程序的文件名和命令行参数的列表。如果此列表中不仅包含文件名,则len(sys.argv)计算结果为大于的整数1,这意味着确实提供了命令行参数。

命令行参数通常用空格分隔,但在这种情况下,您希望将所有参数解释为单个字符串。由于sys.argv是字符串列表,您可以将其传递给join()方法,该方法返回单个字符串值。你不希望这个字符串中出现的程序名称,所以sys.argv你应该传递sys.argv[1:]而不是sys.argv[0:],去除数组的第一个元素。此表达式求值的最终字符串存储在address变量中。

完整的程序加调试log看起来像这样:

#!/usr/bin/python3 # -*- coding: utf-8 -*

#mapit.py launches a map in browser using an address from the command line or clipboard.

#currend directory: Learning\PYTHON\python-auto\100-days\web

import webbrowser, sys, pyperclip
import requests
import traceback
import socket
#设置socket 寻址超时时间
socket.setdefaulttimeout(15)
#导入日志模块
import logging
#LOG_FORMAT = "[ %(asctime)s %(name)s %(levelname)s %(pathname)s ] %(message)s "#配置输出日志格式
LOG_FORMAT = "%(message)s"
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH = None #os.path.join(os.getcwd(),'debugging.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    )

def open_map():
    if len(sys.argv) > 1:
        #Get address from command line
        address = ' '.join(sys.argv[1:]) #以空格' ' 分割
    else:
        #Get address from clipboard
        address = pyperclip.paste()
    
    logging.debug('try to open google map from browser@address in %s.' %(address))
    webbrowser.open('http://www.google.cn/maps/place/' + address)


if __name__ == '__main__':
    open_map()

运行:

python mapit.py 杭州市

2. 用requests模块从Web下载文件

requests 模块让你很容易从Web下载文件,不必担心一些诸如网络错误,链接问题和数据压缩等问题。requests 模块不是Python自带的,所以需要先安装,命令行窗户运行 pip install requests 。

2.1 用requests.get()函数下载一个网页

requests.get()函数接受一个要下载的URL字符串。通过在requests.get()的返回值上调用type(),你可以看到他返回一个Response对象,其中包含了Web服务器对你的请求作出的响应。通过检查Response对象的status_code属性,你可以了解对这个网页的请求是否成功。如果该值等于requests.codes.ok那么表示一切顺利(顺便说一下,HTTP协议中"OK"的状态码是200。你可能已经熟悉04状态码,它表示"没找到")。如果请求成功,下载的页面就作为一个字符串,保存在Response对象的text变量中,这个变量保存了包含一整个文档的内辞工。可以调用len()来获取该字符串的长度。requests.get()传入参数是URL,可以用以下两个,为古登堡计划公益项目的电子图书下载地址:

url = 'https://www.gutenberg.org/files/60176/60176-0.txt'

url = 'https://www.gutenberg.org/cache/epub/1112/pg1112.txt'

#! /usr/bin/python # -*- coding: utf-8 -*

import requests
import logging
import os
#LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
LOG_FORMAT = "%(message)s " #简要的格式,只包含输入文本
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH =  None #os.path.join(os.getcwd(),'FTP_LOGIN.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filemode='w', #覆盖之前的记录 'a'是追加
                    filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    )

def web_request(url):
    #下载TXT文本
    res = requests.get(url)
    try:
        logging.debug('try to get %s ...' %(url))
        res.raise_for_status()
    except Exception as exc:
        print('There was some error in requests.get:' %(exc))
        return False
    
    #走到这里说明下载成功
    #将下载的文件保存到本地
    logging.debug('get book success!, try to save be Unicode file now...')
    save_path = os.path.join(os.getcwd(), 'Dancers_in_the_Dark.txt')
    #save_path = os.path.join(os.getcwd(), 'THE_TRAGEDY_OF_ROMEO_AND_JULIET.txt')
    with open(save_path, 'wb') as playFile: #用 with open as 打开不需要调用close(),以写或覆盖的模式打开二进制文件
        for words in res.iter_content(100000):
            wt_len = playFile.write(words)
            logging.debug('%d bytes writed...' %(wt_len))
    logging.debug('Done!, please open %s to check completting!' %(save_path))

def call_request():
    url = 'https://www.gutenberg.org/files/60176/60176-0.txt'
    #url = 'https://www.gutenberg.org/cache/epub/1112/pg1112.txt'
    web_request(url)

if '__main__' == __name__:
    call_request()

2.2 检查错误

Response对象有一个status_code属性,可以检查它是否等于requests.code.ok,了解是否下载成功,检查成功有一种简单的方法,如上述代码中所示,在Response对象上调用raise_for_status()方法。如果下载文件出错,将抛出异常。raise_for_status()方法是一种很好的方式,确保程序在下载失败时停止。这是一件好事,你希望程序在发生未预期的错误时,及时停止。如果下载失败对于程序来说不够严重,可以用try和expect语句将raise_for_status()代码行包装起来,正确处理这一错误,不至于让程序直接崩溃。

2.3 将下载的文件保存到硬盘

将从Web下载的文件写入硬盘,可以用标准的open()函数和write()方法,但是稍有不同,这里打开文件时最好用“写二进制”模式打开该文件, 即便你下载的文件时纯文本的,你也需要写入二进制数据,而不是文本数据,目的是为了保存该文本的“Unicode编码”。百科:Unicode是一个编码方案,Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode 编码共有三种具体实现,分别为utf-8,utf-16,utf-32,其中utf-8占用一到四个字节,utf-16占用二或四个字节,utf-32占用四个字节。目前Unicode 码在全球范围的信息交换领域均有广泛的应用。

打开文件的方式:

with open(save_path, 'wb') as playFile:

   

下面是下载和保存文件的完整过程:

  1. 调用requests.get()下载文件。

  2. 调用open()'wb'以写入二进制模式创建一个新文件。

  3. 循环遍历Response对象的iter_content()方法。

  4. 调用write()将每次迭代内容写入文件。

  5. 调用close()以关闭文件。

3. 用BeautifulSoup模块解析HTML

Beautiful Soup是一个用于从HTML页面提取信息的模块(为此目的,它比正则表达式要好得多)。该BeautifulSoup模块的名称是bs4(对于Beautiful Soup,版本4)。要安装它,您将需要从命令行运行pip install beautifulsoup4。(有关安装第三方模块的说明,请beautifulsoup4参阅附录A。)虽然是用于安装的名称,但要导入要导入的Beautiful Soup import bs4。

对于本章,Beautiful Soup示例将解析硬盘驱动器上的HTML文件(即,分析并标识其中的各个部分)。在IDLE中打开一个新的文件编辑器窗口,输入以下内容,并将其另存为example.html。或者,从http://nostarch.com/automatestuff/下载。

<!-- This is the example.html example file. -->

<html><head><title>The Website Title</title></head>
<body>
<p>Download my <strong>Python</strong> book from <a href="http://
inventwithpython.com">my website</a>.</p>
<p class="slogan">Learn Python the easy way!</p>
<p>By <span id="author">Al Sweigart</span></p>
</body></html>

3.1 从HTML创建一个BeautifulSoup对象

bs4.BeautifulSoup()需要使用包含将要解析的HTML的字符串来调用该函数。该bs4.BeautifulSoup()函数返回的是一个BeautifulSoup对象。当您的计算机连接到Internet时,使用bs4模块打开html文件,当然也可以直接下载html文件进行解析,先了解以下几个选择器的使用方式:

 表3-1 CSS选择器的例子

传递给select()方法的选择器将匹配的结果
soup.select('div')所有名为<div>的元素
soup.select('#author')带有id属性为author的元素
soup.select('.notice')所有使用CSS class属性名为notice的元素
soup.select('div span')所有在<div>元素之内的<span>元素
soup.select('div > span')所有直接在<div>元素之内的<span>元素,中间没有其他元素
soup.select('input[name]')所有名为<input>,并有一个name属性,其值无所谓的元素
soup.select('input[type="button"]')所有名为<input>,并有一个type属性,其值为 button的元素

现在,我们打开之前的example.html并使用bs4模块对其进行解析

#! /usr/bin/python # -*- coding: utf-8 -*

import requests
import logging
import os, sys, bs4
#LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
LOG_FORMAT = "%(message)s " #简要的格式,只包含输入文本
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH =  None #os.path.join(os.getcwd(),'FTP_LOGIN.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filemode='w', #覆盖之前的记录 'a'是追加
                    filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    )

def get_html_file():
    url = 'http://nostarch.com'
    res = requests.get(url)  #一般情况下访问不了
    try:
        logging.debug('try to get %s ...' %(url))
        res.raise_for_status()
    except Exception as exc:
        logging.debug('There was some error in requests.get from web:%s.:' %(exc, url))
        return False
    #examsoup = bs4.BeautifulSoup(res.text)

def phrase_html_file():
    with open('example.html') as  examhtml:
        examsoup = bs4.BeautifulSoup(examhtml.read(), features="html.parser")  #如果不指定features,在不同系统可能使用不同的解析器,此行造成以下告警内容,,提示十分友好
        """
        search.py:30: UserWarning: No parser was explicitly specified,
         so I'm using the best available HTML parser for this system ("html.parser"). 
         This usually isn't a problem, but if you run this code on another system, 
         or in a different virtual environment, it may use a different parser and behave differently.
        The code that caused this warning is on line 30 of the file search.py. 
        To get rid of this warning, pass the additional argument 'features="html.parser"' to the BeautifulSoup constructor.
        """
        elem_author = examsoup.select('#author')
        print('type(author) = %s' %(type(elem_author)) )
        print('len(elem_author) = %d' %(len(elem_author)) )
        for i in range(len(elem_author)):
            print('type(elem_author[%d]) = %s' %(i, type(elem_author[i])) )
            print('str(elem_author[%d]) = %s' %(i, str(elem_author[i])) )
            print('elem_author[%d].getText() = %s' %(i, elem_author[i].getText() ) )
            print('elem_author[%d].attrs = %s' %(i, elem_author[i].attrs) )


if __name__ == '__main__':
    phrase_html_file()

输出结果:

type(author) = <class 'list'>
len(elem_author) = 1
type(elem_author[0]) = <class 'bs4.element.Tag'>
str(elem_author[0]) = <span id="author">Al Sweigart</span>
elem_author[0].getText() = Al Sweigart
elem_author[0].attrs = {'id': 'author'}

这段代码将带有“id = author”的元素,从式例HTML中找出来,我们使用sleect('#author')返回一个列表,其中包含带有"id = author"的元素。我们将这个Tag对象的列表保存在变量elem_author中,len(elem_author)方法告诉我们列表中只有一个Tag对象,只有一次匹配。在该元素上调用getText()方法,返回该元素的文本,或内部的HTML。一个元素的文本是在开始和结束标签之间的内容:在这个例子中,就是'AI Sweigart'。将该元素传递给str将返回一个字符串,其中包含开始和结束标签,以及该元素的文本。最后,attrs给了我们一个字典,包含钙元素的属性'id'及属性的值'author'。

下面再从例子中找出元素<p>:

#! /usr/bin/python # -*- coding: utf-8 -*

import requests
import logging
import os, sys, bs4
#LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
LOG_FORMAT = "%(message)s " #简要的格式,只包含输入文本
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH =  None #os.path.join(os.getcwd(),'FTP_LOGIN.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filemode='w', #覆盖之前的记录 'a'是追加
                    filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    )

def get_html_file():
    url = 'http://nostarch.com'
    res = requests.get(url)  #一般情况下访问不了
    try:
        logging.debug('try to get %s ...' %(url))
        res.raise_for_status()
    except Exception as exc:
        logging.debug('There was some error in requests.get from web:%s.:' %(exc, url))
        return False
    #examsoup = bs4.BeautifulSoup(res.text)

def phrase_html_file():
    with open('example.html') as  examhtml:
        examsoup = bs4.BeautifulSoup(examhtml.read(), features="html.parser")  #如果不指定features,在不同系统可能使用不同的解析器,此行造成以下告警内容,,提示十分友好
        """
        search.py:30: UserWarning: No parser was explicitly specified,
         so I'm using the best available HTML parser for this system ("html.parser"). 
         This usually isn't a problem, but if you run this code on another system, 
         or in a different virtual environment, it may use a different parser and behave differently.
        The code that caused this warning is on line 30 of the file search.py. 
        To get rid of this warning, pass the additional argument 'features="html.parser"' to the BeautifulSoup constructor.
        """
        print('NOW SEARCH THE ELEMENT NAME AS <p>:-----------------------------')
        print("""
            <!-- This is the example.html example file. -->

            <html><head><title>The Website Title</title></head>
            <body>
            <p>Download my <strong>Python</strong> book from <a href="http://
            inventwithpython.com">my website</a>.</p>
            <p class="slogan">Learn Python the easy way!</p>
            <p>By <span id="author">Al Sweigart</span></p>
            </body></html>
        """)
        """ 找出<p>元素 """
        elem_p = examsoup.select('p')
        print('type(elem_p) = %s' %(type(elem_p)) )
        print('len(elem_p) = %d' %(len(elem_p)) )
        for i in range(len(elem_p)):
            print('type(elem_p[%d]) = %s' %(i, type(elem_p[i])) )
            print('str(elem_p[%d]) = %s' %(i, str(elem_p[i])) )
            print('elem_p[%d].getText() = %s' %(i, elem_p[i].getText() ) )
            print('elem_p[%d].attrs = %s' %(i, elem_p[i].attrs) )

        


if __name__ == '__main__':
    phrase_html_file()

结果:

NOW SEARCH THE ELEMENT NAME AS <p>:-----------------------------

            <!-- This is the example.html example file. -->

            <html><head><title>The Website Title</title></head>
            <body>
            <p>Download my <strong>Python</strong> book from <a href="http://
            inventwithpython.com">my website</a>.</p>
            <p class="slogan">Learn Python the easy way!</p>
            <p>By <span id="author">Al Sweigart</span></p>
            </body></html>

type(elem_p) = <class 'list'>
len(elem_p) = 3
type(elem_p[0]) = <class 'bs4.element.Tag'>
str(elem_p[0]) = <p>Download my <strong>Python</strong> book from <a href="http://
inventwithpython.com">my website</a>.</p>
elem_p[0].getText() = Download my Python book from my website.
elem_p[0].attrs = {}
type(elem_p[1]) = <class 'bs4.element.Tag'>
str(elem_p[1]) = <p class="slogan">Learn Python the easy way!</p>
elem_p[1].getText() = Learn Python the easy way!
elem_p[1].attrs = {'class': ['slogan']}
type(elem_p[2]) = <class 'bs4.element.Tag'>
str(elem_p[2]) = <p>By <span id="author">Al Sweigart</span></p>
elem_p[2].getText() = By Al Sweigart
elem_p[2].attrs = {}

这一次,select()给我们返回一个列表,包含3个元素,即有3次匹配,我们将它保存在elem_p中,在elem_p[0],elem_p[1],elem_p[2]上使用str(),将每个元素显示为一个字符串,在每个元素上使用getText()显示它的文本。

注意,属性并不是每个元素都存在的,只有在存在属性的元素中才能得到属性。

通过元素属性也可以获取数据

Tag对象的get()方法让我们很容易从元素中获取属性值,向该方法传入一个属性名称的字符串,它将返回该属性的值。

例如:

>>> spanElem = soup.select('span')[0] 
>>> spanElem.get('id')
'author'

4. 小项目《1》:查找一个话题并针对每个结果打开浏览器界面

import sys, webbrowser, requests, bs4
# 从参数列表中获取关键词
keywords = '%20'.join(sys.argv[1:])
 
# 进行百度搜索并下载搜索页面
fakeua = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36"}
searchPage = requests.get('https://www.baidu.com/s?wd=' + keywords, headers = fakeua)
searchPage.raise_for_status()    # 如果失败就抛出异常
 
# 得到前5个搜索结果的链接
searchSoup = bs4.BeautifulSoup(searchPage.text, features="html.parser")
elements = searchSoup.select('.t a')
 
# 在浏览器中打开这些连接
for i in range(min(5, len(elements))):
    webbrowser.open(elements[i].get('href'))

获取命令行参数使用sys.argv[1:] 

5. 小项目《2》下载所有XKCD漫画

博客和其他定期更新的网站通常会在首页上显示最新的帖子,并在页面上的“上一页”按钮将您带到上一页。然后,该帖子也将具有“上一个”按钮,依此类推,从而创建从最新页面到站点上第一篇帖子的跟踪。如果您想在不在线时阅读网站内容的副本,则可以手动浏览每个页面并保存每个页面。但这是一件很无聊的工作,所以让我们编写一个程序来代替它。

XKCD是一个流行的极客漫画网站,其网站符合此结构(请参看图5-1)。位于http://xkcd.com/的首页上有一个“上一页”按钮,可引导用户浏览以前的漫画。手动下载每个漫画将花费很多时间,但是您可以编写脚本在几分钟内完成此操作。

                                                                                           图5-1

这是您的程序的作用:

  • 加载XKCD主页。

  • 将漫画图像保存在该页面上。

  • 跟随上一个漫画链接。

  • 重复直到到达第一个漫画。

这意味着您的代码将需要执行以下操作:

  • 下载带有requests模块的页面。

  • 使用Beautiful Soup查找页面的漫画图像的URL。

  • 使用下载并保存漫画图像到硬盘iter_content()

  • 找到上一个漫画链接的URL,然后重复。

打开一个新的文件编辑器窗口,并将其另存为downloadXkcd.py

首先你需要找到漫画在网页中的准确位置元素:如下图5-2所示

                                                                                   图5-2

 

通过使用开发人员工具检查XKCD主页,您知道<img>漫画图像的<div>元素位于id属性设置为的元素内部comic,因此选择器'#comic img'将从对象中获取正确的<img>元素BeautifulSoup

一些XKCD页面具有特殊的内容,而不是简单的图像文件。没关系; 您将跳过这些。如果您的选择器找不到任何元素,soup.select('#comic img')则将返回一个空白列表。发生这种情况时,程序可以仅打印错误消息并继续运行而无需下载图像。

否则,选择器将返回一个包含一个<img>元素的列表。您可以src从此<img>元素获取属性,并将其传递requests.get()给下载漫画的图像文件。

comicUrl值类似'http://imgs.xkcd.com/comics/heartbleed_explanation.png'—您可能已经注意到,它看起来很像文件路径。而事实上,你可以对comicUrl调用os.path.basename(),它将只返回URL的最后一部分:'heartbleed_explanation.png'。将图像保存到硬盘时,可以将其用作文件名。您使用将该名称与xkcd文件夹名称连接在一起,os.path.join()以便您的程序在Windows上使用反斜杠(\),在OS X和Linux上使用正斜杠(/)。现在您终于有了文件名,您可以调用open()'wb'“写入二进制”模式打开一个新文件。

请记住,在本章前面的内容中,要保存使用请求下载的文件,您需要遍历该iter_content()方法的返回值。for循环中的代码将图像数据的块(每个最大为100000个字节)写出到文件中,然后关闭文件。图像现在已保存到硬盘驱动器。

之后,选择器'a[rel="prev"]'标识属性设置为的<a>元素,您可以使用该元素的属性来获取先前漫画的URL,该URL存储在中。然后,循环将再次开始针对该漫画的整个下载过程。relprev<a>hrefurlwhile

漫画的上一页按钮在开发者元素中如图5-3所示

                                                                                                          图5-3

代码如下:

import sys, os, requests, bs4
import logging
import traceback

LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH = None #os.path.join(os.getcwd(),'debugging.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    )
#downloadxkcd.py - Download every sigle XKCD comic.

"""
soup.select('div')	所有名为<div>的元素
soup.select('#author')	带有id属性为author的元素
soup.select('.notice')	所有使用CSS class属性名为notice的元素
soup.select('div span')	所有在<div>元素之内的<span>元素
soup.select('div > span')	所有直接在<div>元素之内的<span>元素,中间没有其他元素
soup.select('input[name]')	所有名为<input>,并有一个name属性,其值无所谓的元素
soup.select('input[type="button"]')	所有名为<input>,并有一个type属性,其值为 button的元素

"""


def downloadxkcd():
    fakeua = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36"}
    url = 'https://xkcd.com'    #starting url
    orig_url = url
    os.makedirs('xkcd', exist_ok = True)    #store comics in ./xkcd
    try:
        while not  url.endswith('#'):

            # TODO: Download the page.

            logging.debug('downloading page %s ...' %(url))
            res = requests.get(url, headers = fakeua)
            res.raise_for_status()
            soup = bs4.BeautifulSoup(res.text, features="html.parser")
            #如果不指定features,在不同系统可能使用不同的解析器,此行造成以下告警内容,,提示十分友好

            # TODO: FInd the url of the comic image.

            comicElem = soup.select('#comic img') #所有在 comic 内的img
            if [] == comicElem:
                print('could not find comic image.')
            else:
                # TODO: Download the image.  
                comicUrl = 'https:'+ comicElem[0].get('src')
                print('Downloadin image %s .................' %(comicUrl) ) 
                res = requests.get(comicUrl, headers = fakeua)
                res.raise_for_status()

            # TODO: Savethe image to ./xkcd
            with open(os.path.join('xkcd', os.path.basename(comicUrl)), 'wb') as imageFile:
                for chunk in res.iter_content(100000):
                    imageFile.write(chunk)

            # TODO: Get the Prev button's url
            prevLink = soup.select('a[rel="prev"]')[0]
            prev_image_url = prevLink.get('href')
            if None == prev_image_url:
                print('get prev image url result is None.')
                break
            else:
                url = orig_url + prev_image_url
        print('Done!')
    except:
        logging.debug('some error happend %s' %(traceback.format_exc()) )




if __name__ == '__main__':
    downloadxkcd()

效果如下:

下载结果:

6. 自动化网页任务——selenium模块

requests和BeautifulSoup模块很了不起,只要你能弄清楚需要传递给requests.get()的URL。但是有时候这并不容易找到,或者你即将浏览的网站需要你先登录,selenium模块将让你的程序具有执行这种复杂任务的能力。通过该selenium模块,Python可以通过编程方式单击链接并填写登录信息来直接控制浏览器,几乎就像是人类在与页面进行交互一样。Selenium允许您以比Requests and Beautiful Soup更高级的方式与网页交互;但是由于它启动了Web浏览器,因此,例如,您只需要从Web下载一些文件,它就会变慢并且很难在后台运行。

以下例子使用Google浏览器完成,也可以使用火狐浏览器。

6.1 驱动Chrome浏览器

使用Python驱动Google浏览器完成网页访问,您需要安装Google浏览器,并将Google浏览器额驱动程序ChromeDriver同样安装到Google浏览器安装目录下:Google浏览器驱动程序ChromeDriver可以从ChromeDriver仓库下载对应版本,下载完以后,解压到Google浏览器应用目录下即可,我的目录如下:

Google浏览器版本为:76.0.3809.100,ChromeDriver下载对应版本:均可

现在,准备工作完成后,就可以使用Python驱动浏览器了。不过,即便这样,您还需要设置一些环境,比如讲Google浏览器的安装目录,即ChromeDriver驱动目录添加到您Windows的环境变量Path中,不过也可以不添加,那么久需要在您的Python程序中Web驱动初始化的时候指定chromedriver.exe的路径,例如:

from selenium import webdriver 
#浏览器初始化设置为谷歌
driver = webdriver.Chrome('C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe')

这样,您就初始化了一个Chrome浏览器的驱动对象。

下面来使用这个对象做一些简单的事情,比如打开浏览器访问一个网站:

#! /usr/bin/python3
import logging,os
import traceback
from selenium import webdriver 
#浏览器初始化设置为谷歌
driver = webdriver.Chrome('C:/Program Files (x86)/Google/Chrome/Application/chromedriver.exe')
driver.maximize_window() #浏览器窗口最大化


LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH = None #os.path.join(os.getcwd(),'debugging.log')
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filename=LOG_PATH, #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
                    filemode='w'
                    )

def browser_python(url):
    print('try to conect to %s...' %(url))
    try:
        driver.get(url)
    except:
        print('some bad happend :%s' %(traceback.format_exc()) )




if __name__ == '__main__':
    #url = 'http://inventwithpython.com/'
    #browser_python(url) #访问python 自动化编程的电子书网站
    #访问我的QQ空间,这里需要登录,关于登录程序暂时不添加
    url = 'https://user.qzone.qq.com/2281284114/infocenter'
    browser_python(url)

你会注意到,当webdriver.Chrome()调用时,浏览器启动了,如果不添加任何动作,那么将初始化一个Chrome浏览器空白页。

对值driver调用type(),得到数据类型为:<class 'selenium.webdriver.chrome.webdriver.WebDriver'>,数据类型为WebDriver。

6.2 在Web页面中寻找元素

WebDriver对象有多种方法,用于在页面中寻找元素。他们被分成find_element_*和find_elements_*方法。find_element_*返回一个WebElement对象,代表页面中匹配查询的第一个元素,find_elements_*返回WebElement_*对象的列表。包含页面中所有匹配的元素。

表6-1 列出了find_element_*和find_elements_*方法的几个例子,他们在变量driver中保存的WebDriver对象上调用。

表6-1 selenium的WebDriver方法,用于寻找元素
WebElement对象/列表返回
browser.find_element_by_class_name(name) 
browser.find_elements_by_class_name(name)使用CSS类name的元素
browser.find_element_by_css_selector(selector) 
browser.find_elements_by_css_selector(selector)符合CSS selector的元素
browser.find_element_by_id(id) 
browser.find_elements_by_id(id)匹配id属性值的元素
browser.find_element_by_link_text(text) 
browser.find_elements_by_link_text(text)与text提供的内容完全匹配的<a>元素
browser.find_element_by_partial_link_text(text) 
browser.find_elements_by_partial_link_text(text)包含text提供的内容的<a>元素
browser.find_element_by_name(name) 
browser.find_elements_by_name(name)匹配name属性值的元素
browser.find_element_by_tag_name(name) 
browser.find_elements_by_tag_name(name)匹配标签name的<a>元素(不区分大小写;<a>元素匹配'a'和'A')

*_by_tag_name()方法外,所有方法的参数均区分大小写。如果页面上不存在与该方法要查找的内容匹配的元素,则该selenium模块将引发NoSuchElement异常。如果您不希望该异常导致程序崩溃,请在代码中添加tryexcept语句。

一旦有了WebElement对象,就可以读取表6-2宗的属性或调用其中的方法,了解WebElement对象的更多内容

表 6-2 WebElement的属性和方法
属性或方法描述
tag_name标签名称,例如'a'表示<a>元素
get_attribute(name)该元素name属性的值
text元素内的文本,例如<span>hello</span>中的hello
clear()对于文本字段或文本区域元素,清除键入其中的文本
is_displayed()元素可见,返回True;否则返回False
is_enabled()对于输入元素,如果已启用该元素,则返回True;否则返回False 
is_selected()对于复选框或单选框元素,如果该元素被选中,返回True;否则返回false
location一个字典,包含键'x'和'y',表示元素在页面中的位置 的字典

6.3 点击页面

find_element_*和find_elements_*方法返回的WebElement对象有一个click()方法,模拟鼠标在该元素上点击。这个方法可以用于链接跳转,选择单选按钮,点击提交按钮,或者处罚钙元素被鼠标点击时发生的任何事情。例子:比如我们访问 http://inventwithpython.com/ ,首页有对应每一本书籍的在线阅读选项框,点击就会跳转到该书籍的电子在线阅读界面:我们将鼠标放在'Read Online for Free'选项框上,右键选择检查,就会跳出该选项的开发者界面,这样我们就知道我们将要获取的链接关键字和链接了

def click_link(url):
    try:
        #先访问网页,返回WebElement
        driver.get(url)
        #其实是寻找href
        '''
        <a href="https://automatetheboringstuff.com/" class="btn btn-primary">Read Online for Free</a>
        '''
        link_login = driver.find_element_by_link_text('Read Online for Free') #找到并返回第一个
        link_login.click() #模拟点击
    except:
        print('some bad happend :%s' %(traceback.format_exc()) )

网页成功跳转到在线阅读页面:https://automatetheboringstuff.com/

6.4 填写并提交表单(模拟自动登录QQ空间)

向Web页面的文本字段发送击键,只要找到那个文本字段的<input> 或者 <textarea>元素,然后调用send_keys()方法。我们可以利用python模拟邓丽QQ空间或QQ邮箱。这里要注意几点:

1> iframe 框架切换 具体使用可以参考 selenium中iframe的切换

百度:IFRAME是HTML标签,作用是文档中的文档,或者浮动的框架(FRAME)。iframe元素会创建包含另外一个文档的内联框架(即行内框架)

核心属性

属性

描述

class

classname

规定元素的类名(classname)

id

id

规定元素的特定id

style

style_definition

规定元素的行内样式(inline style)

title

text

规定元素的额外信息(可在工具提示中显示)

如QQ空间登录界面的frame 框架

2> 定位input元素,键入内容

测试程序:目前QQ登录需要验证滑动条,因此不能一步登录,但是基本功能已经完成,即:打开浏览器,切换到账号密码登录界面,输入账号密码,只需要手动滑动验证码就可以登录

代码如下:

def login_count():
    try:
        driver.get('https://qzone.qq.com/')
        '''
        定位frame 
        <iframe id="login_frame" name="login_frame" height="100%" scrolling="no" width="100%" frameborder="0" 
        src="https://xui.ptlogin2.qq.com/cgi-bin/xlogin?proxy_url=https%3A//qzs.qq.com/qzone/v6/portal/proxy.html&amp;
        daid=5&amp;&amp;hide_title_bar=1&amp;low_login=0&amp;qlogin_auto_login=1&amp;
        no_verifyimg=1&amp;link_target=blank&amp;appid=549000912&amp;style=22&amp;
        target=self&amp;s_url=https%3A%2F%2Fqzs.qq.com%2Fqzone%2Fv5%2Floginsucc.html%3Fpara%3Dizone&amp;
        pt_qr_app=手机QQ空间&amp;pt_qr_link=https%3A//z.qzone.com/download.html&amp;
        self_regurl=https%3A//qzs.qq.com/qzone/v6/reg/index.html&amp;
        pt_qr_help_link=https%3A//z.qzone.com/download.html&amp;pt_no_auth=0"></iframe>
        '''
        #登录表单在页面的框架中,所以要切换到该框架
        driver.switch_to.frame('login_frame') #没有此条语句将不能跳转登录输入
        login_switch = driver.find_element_by_id('switcher_plogin')
        login_switch.click() #点击通过账号密码交互登录
        InName = driver.find_element_by_id('u')
        InName.clear() #清除框内的内容
        InName.send_keys('123456789')
        inPass = driver.find_element_by_id('p') #输入密码
        inPass.clear()
        inPass.send_keys('******')
        #inPass.submit() 提交表单,实际应用中不常用到,登录界面都是多种多样的
        login_click = driver.find_element_by_id('login_button')
        login_click.click() #登录
    except:
        print('some bad happend :%s' %(traceback.format_exc()) )

6.5 发送特殊键

Selenium有一个用于键盘键的模块,这些模块无法键入字符串值,其功能很像转义符。这些值存储在selenium.webdriver.common.keys模块的属性中。由于这是一个很长的模块名称,因此from selenium.webdriver.common.keys import Keys在程序顶部运行会容易得多。如果这样做的话,那么您只需输入Keys.*** 即可。

表 6-3 列出了常用的Keys变量

表6-3 selenium.webdriver.common.keys模块中常用的变量
属性含义
Keys.DOWN,Keys.UP,Keys.LEFT,Keys.RIGHT键盘方向键
Keys.ENTER, Keys.RETURN回车键和换行键
Keys.HOME,Keys.END,Keys.PAGE_DOWN,Keys.PAGE_UPhome键,end键,PageUp键,PageDown键
Keys.ESCAPE,Keys.BACK_SPACE,Keys.DELETEEsc键,Backspace键和删除键delete
Keys.F1,Keys.F2,...,Keys.F12键盘顶部F1到F12键
Keys.TABTab键

6.6 点击浏览器按钮

方法功能
browser.back()单击上一步或返回按钮
browser.forward()点击前进按钮
browser.refresh()点击刷新/重新加载按钮
browser.quit()点击关闭窗口按钮

关于selenium 的更多信息

selenuum作用远远超出了此处描述的功能。它可以修改浏览器的Cookie,截取网页的屏幕截图以及运行自定义JavaScript。要了解有关这些功能的更多信息,可以访问http://selenium-python.readthedocs.org/上的Selenium文档。

小结

大多数无聊的任务不仅限于计算机上的文件。能够以编程方式下载网页会将您的程序扩展到Internet。该requests模块使下载变得简单明了,并且具有HTML概念和选择器的一些基本知识,您可以利用该BeautifulSoup模块来解析下载的页面。

但是要完全自动化任何基于Web的任务,您需要通过该selenium模块直接控制Web浏览器。该selenium模块将允许您登录网站并自动填写表格。由于Web浏览器是通过Internet发送和接收信息的最常用方法,因此这是您的程序员工具包中的一项强大功能。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值