有些网页刚打开时,并不是所有的内容会立即加载,而是通过javascript进行异步加载,那么上一节的方法就不再适用了。

可以看看网页https://www.chrisburkard.com/,一开始只加载一部分,后面慢慢加载完整内容。

import requests
from bs4 import BeautifulSoup

url = "http://www.chrisburkard.com"

web_r = requests.get(url)
web_soup = BeautifulSoup(web_r.text, 'html.parser')

print(web_soup)
print(web_soup.findAll("img"))  # []

上面这段代码可以看到,返回的内容为空
要解决这个问题,我们需要引入一个新的库 selenium

安装

pip install selenium

selenium支持Firefox, Chrome等浏览器

下面以Firefox为例来完成,执行初始化时可能会报错

webdriver.Firefox()

selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.

原因:使用pip安装selenium,默认安装的是最新版本的selenium,使用pip list查了一下我的selenium版本,是3.14.1,firefox版本,是62.0.2。
selenium 3.x开始,webdriver/firefox/webdriver.py的__init__中,executable_path="geckodriver";而2.x是executable_path="wires"

Firefox 47以上版本,需要下载第三方driver,即geckodriver;在http://docs.seleniumhq.org/download/的Third Party Drivers, Bindings, and Plugins下面找到Mozilla GeckoDriver,下载到任意电脑任意目录,解压后将该路径加入到PC的path(针对Windows)即可。

方法一:可以卸载现有的selenium,安装指定的2.X版本的selenium
方法二:下载geckodriver.exe,下载地址:https://github.com/mozilla/geckodriver/releases,根据自己的电脑,下载的win64位的;

三种方法可以使用这个geckodriver.exe:

  • 在系统环境path里添加路径 (未尝试)
  • 初始化时添加参数executable_path
driver = webdriver.Firefox(executable_path = r"e:\Computer\virtualenv\webscrapping\Firefox\geckodriver.exe")
  • 代码中添加设置环境变量,这个chrome上验证通过,firefox还没试

抓取图片

抓取页面图片的代码如下

import requests
from bs4 import BeautifulSoup
from selenium import webdriver

url = "http://www.chrisburkard.com/"

driver = webdriver.Firefox(executable_path = r"e:\Computer\virtualenv\webscrapping\Firefox\geckodriver.exe")
driver.get(url)
html = driver.execute_script("return document.documentElement.outerHTML")
sel_soup = BeautifulSoup(html, 'html.parser')

images = []
for i in sel_soup.findAll("img"):
    print(i)

webdriver.Firefox()命令会打开Firefox窗口,访问页面
输出结果如下

<img alt="2013, CHRIS BURKARD, ALEUTIAN ISLANDS, ALEUTIANS, JOSH MULCOY, ALEX GRAY, PETE DEVRIES" class="sm-image sm-tile-limit-height" data-clientid="sm-image-model_2" id="sm-tile -image-yui_3_8_0_1_1537699519411_98" itemprop="image" src="https://photos.smugmug.com/Home-Page-Slideshow/n-HbxKw/i-2Fmqc75/0/00026948/X3/i-2Fmqc75-X3.jpg" title=""/>
<img alt="Quantcast" border="0" height="1" src="//pixel.quantserve.com/pixel/p-33GsugQTR-pOM.gif" width="1"/>

可以进一步把图片源从里面提取出来

images = []
for i in sel_soup.findAll("img"):
    print(i)
    src = i["src"]
    images.append(src)
print(images)

结果如下

['https://photos.smugmug.com/Home-Page-Slideshow/n-HbxKw/i-2Fmqc75/0/00026948/X3/i-2Fmqc75-X3.jpg', '//pixel.quantserve.com/pixel/p-33GsugQTR-pOM.gif']

i是什么数据类型,能够支持这种访问操作?

for i in sel_soup.findAll("img"):
    print(type(i))
    print(dir(i)) 

查看它的类型结果如下

<class 'bs4.element.Tag'>

['HTML_FORMATTERS', 'XML_FORMATTERS', '__bool__', '__call__', '__class__', '__contains__', '__copy__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__
format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__ ', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', '_all_strings', '_attr_value_as_string', '_attribute_checker', '_find_all', '_find_one', '_formatter_for_name', '_is_xml', '_lastRecursiveChild', '_last_descendant', '_select_debug', '_selector_combinators', '_should_pretty_print', '_tag_name_matches_and', 'append', 'attribselect_re', 'attrs', 'can_be_empty_element', 'childGenerator', 'children', 'clear', 'contents', 'decode', 'decode_c 
ontents', 'decompose', 'descendants', 'encode', 'encode_contents', 'extract', 'fetchNextSiblings', 'fetchParents', 'fetchPrevious', 'fetchPreviousSiblings', 'find', 'findAll', 'fin
dAllNext', 'findAllPrevious', 'findChild', 'findChildren', 'findNext', 'findNextSibling', 'findNextSiblings', 'findParent', 'findParents', 'findPrevious', 'findPreviousSibling', 'f indPreviousSiblings', 'find_all', 'find_all_next', 'find_all_previous', 'find_next', 'find_next_sibling', 'find_next_siblings', 'find_parent', 'find_parents', 'find_previous', 'find_previous_sibling', 'find_previous_siblings', 'format_string', 'get', 'getText', 'get_attribute_list', 'get_text', 'has_attr', 'has_key', 'hidden', 'index', 'insert', 'insert_afte
r', 'insert_before', 'isSelfClosing', 'is_empty_element', 'known_xml', 'name', 'namespace', 'next', 'nextGenerator', 'nextSibling', 'nextSiblingGenerator', 'next_element', 'next_elements', 'next_sibling', 'next_siblings', 'parent', 'parentGenerator', 'parents', 'parserClass', 'parser_class', 'prefix', 'preserve_whitespace_tags', 'prettify', 'previous', 'previousGenerator', 'previousSibling', 'previousSiblingGenerator', 'previous_element', 'previous_elements', 'previous_sibling', 'previous_siblings', 'quoted_colon', 'recursiveChildGenerator', 'renderContents', 'replaceWith', 'replaceWithChildren', 'replace_with', 'replace_with_children', 'select', 'select_one', 'setup', 'string', 'strings', 'stripped_strings', 'tag_name_re', 'text', 'unwrap', 'wrap']

存储图片

对于抓取到的图片,接下来我们会把它存到当前目录下的images文件夹下,文件名保持不变,代码如下

import os
import shutil

current_path = os.getcwd()
for img in images:
    try:
        file_name = os.path.basename(img)
        img_r = requests.get(img, stream=True)
        new_path = os.path.join(current_path, "images", file_name)
        with open(new_path, "wb") as output_file:
            shutil.copyfileobj(img_r.raw, output_file)
        del img_r
    except:
        pass

几个地方说明一下

  • os的用法,包括getcwd获取当前路径, path.basename获取最后的文件名,path.join连接路径
  • 文件拷贝,调用了方法shutil.copyfileobj
  • 打开图片时,如果是大图片,需要设置stream=True,否则会出错,具体可参考文档用法响应体内容工作流 或者 https://stackoverflow.com/questions/16694907/how-to-download-large-file-in-python-with-requests-py
  • 打开文件时使用wb方式,二进制覆盖

抓取结果中看到,只有一个图片(相对路径的图片并没有保存),原因是有些图片的加载还没有完成,这个时候我们可以等待一会儿sleep之后再继续访问

下面代码示例结果中可以看到,后面的循环迭代中的图片数量在递增的,当然实际工作中,我们不需要每次迭代都去抓取图片

iter = 0
while iter < 10:
    html = driver.execute_script("return document.documentElement.outerHTML")
    sel_soup = BeautifulSoup(html, 'html.parser')
    images = []
    for i in sel_soup.findAll("img"):
        print(type(i))
        src = i["src"]
        images.append(src)
    current_path = os.getcwd()
    for img in images:
        try:
            file_name = os.path.basename(img)
            img_r = requests.get(img, stream=True)
            new_path = os.path.join(current_path, "images", file_name)
            with open(new_path, "wb") as output_file:
                shutil.copyfileobj(img_r.raw, output_file)
            del img_r
        except:
            pass
    iter += 1
    time.sleep(5)

关闭

driver.quit()

评论