以视频爬取实例讲解Python爬虫神器Beautiful Soup用法


Posted in Python onJanuary 20, 2016

1.安装BeautifulSoup4
easy_install安装方式,easy_install需要提前安装

easy_install beautifulsoup4

pip安装方式,pip也需要提前安装.此外PyPi中还有一个名字是 BeautifulSoup 的包,那是 Beautiful Soup3 的发布版本.在这里不建议安装.

pip install beautifulsoup4

Debain或ubuntu安装方式

apt-get install Python-bs4

你也可以通过源码安装,下载BS4源码

Python setup.py install

2.小试牛刀

# coding=utf-8
'''
@通过BeautifulSoup下载百度贴吧图片
'''
import urllib
from bs4 import BeautifulSoup
url = 'http://tieba.baidu.com/p/3537654215'

# 下载网页
html = urllib.urlopen(url)
content = html.read()
html.close()

# 使用BeautifulSoup匹配图片
html_soup = BeautifulSoup(content)
# 图片代码我们在[Python爬虫基础1--urllib]( http://blog.xiaolud.com/2015/01/22/spider-1st/ "Python爬虫基础1--urllib")里面已经分析过了
# 相较通过正则表达式去匹配,BeautifulSoup提供了一个更简单灵活的方式
all_img_links = html_soup.findAll('img', class_='BDE_Image')

# 接下来就是老生常谈的下载图片
img_counter = 1
for img_link in all_img_links:
  img_name = '%s.jpg' % img_counter
  urllib.urlretrieve(img_link['src'], img_name)
  img_counter += 1

很简单,代码注释里面已经解释的很清楚了.BeautifulSoup提供了一个更简单灵活的方式,去分析网站源码,更快获取图片link.

3.爬取实例
3.1基本的抓取技术
在写一个爬虫脚本时,第一件事情就是手动观察要抓取的页面来确定数据如何定位。

首先,我们要看一看在 http://pyvideo.org/category/50/pycon-us-2014 上的 PyCon 大会视频列表。检查这个页面的 HTML 源代码我们发现视频列表的结果差不多是长这样的:

<div id="video-summary-content">
  <div class="video-summary">  <!-- first video -->
    <div class="thumbnail-data">...</div>
    <div class="video-summary-data">
      <div>
        <strong><a href="#link to video page#">#title#</a></strong>
      </div>
    </div>
  </div>
  <div class="video-summary">  <!-- second video -->
    ...
  </div>
  ...
</div>

那么第一个任务就是加载这个页面,然后抽取每个单独页面的链接,因为到 YouTube 视频的链接都在这些单独页面上。

使用requests来加载一个 web 页面是非常简单的:

import requests
response = requests.get('http://pyvideo.org/category/50/pycon-us-2014')

就是它!在这个函数返回后就能从response.text中获得这个页面的 HTML 。

下一个任务是抽取每一个单独视频页面的链接。通过 BeautifulSoup 使用 CSS 选择器语法就能完成它,如果你是客户端开发者的话你可能对这会很熟悉。

为了获得这些链接,我们要使用一个选择器,它能抓取在每一个 id 为video-summary-data的<div>中所有的<a>元素。由于每个视频都有几个<a>元素,我们将只保留那些 URL 以/video开头的<a>元素,这些就是唯一的单独视频页面。实现上述标准的 CSS 选择器是div.video-summary-data a[href^=/video]。下面的代码片段通过 BeautifulSoup 使用这个选择器来获得指向视频页面的<a>元素:

import bs4
soup = bs4.BeautifulSoup(response.text)
links = soup.select('div.video-summary-data a[href^=/video]')

因为我们真正关心的是这个链接本身而不是包含它的<a>元素,我们可以使用列表解析来改善上述代码。

links = [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]
现在,我们已经有了一个包含所有链接的数组,这些链接指向了每个单独页面。

下面这段脚本整理了目前我们提到的所有技术:

import requests
import bs4

root_url = 'http://pyvideo.org'
index_url = root_url + '/category/50/pycon-us-2014'

def get_video_page_urls():
  response = requests.get(index_url)
  soup = bs4.BeautifulSoup(response.text)
  return [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]

print(get_video_page_urls())

如果你运行上面这段脚本你将会获得一个满是 URL 的数组。现在我们需要去解析每个 URL 以获得更多关于每场 PyCon 会议的信息。

3.2抓取相连页面
下一步是加载我们的 URL 数组中每一个页面。如果你想要看看这些页面长什么样的话,这儿是个样例:http://pyvideo.org/video/2668/writing-restful-web-services-with-flask。没错,那就是我,那是我会议中的一个!

从这些页面我们可以抓取到会议的标题,在页面的顶部能看到它。我们也可以从侧边栏获得演讲者的姓名和 YouTube 的链接,侧边栏在嵌入视频的右下方。获取这些元素的代码展示在下方:

def get_video_data(video_page_url):
  video_data = {}
  response = requests.get(root_url + video_page_url)
  soup = bs4.BeautifulSoup(response.text)
  video_data['title'] = soup.select('div#videobox h3')[0].get_text()
  video_data['speakers'] = [a.get_text() for a in soup.select('div#sidebar a[href^=/speaker]')]
  video_data['youtube_url'] = soup.select('div#sidebar a[href^=http://www.youtube.com]')[0].get_text()

关于这个函数需要注意的一些事情:

从首页抓取的 URL 是相对路径,所以root_url需要加到前面。
大会标题是从 id 为videobox的<div>里的<h3>元素中获得的。注意[0]是必须的,因为调用select()返回的是一个数组,即使只有一个匹配。
演讲者的姓名和 YouTube 链接的获取方式与首页上的链接获取方式类似。
现在就剩下从每个视频的 YouTube 页面抓取观看数了。接着上面的函数写下去其实是非常简单的。同样,我们也可以抓取 like 数和 dislike 数。

def get_video_data(video_page_url):
  # ...
  response = requests.get(video_data['youtube_url'])
  soup = bs4.BeautifulSoup(response.text)
  video_data['views'] = int(re.sub('[^0-9]', '',
                   soup.select('.watch-view-count')[0].get_text().split()[0]))
  video_data['likes'] = int(re.sub('[^0-9]', '',
                   soup.select('.likes-count')[0].get_text().split()[0]))
  video_data['dislikes'] = int(re.sub('[^0-9]', '', 
                    soup.select('.dislikes-count')[0].get_text().split()[0]))
  return video_data

上述调用soup.select()函数,使用指定了 id 名字的选择器,采集到了视频的统计数据。但是元素的文本需要被处理一下才能变成数字。考虑观看数的例子,在 YouTube 上显示的是"1,344 views"。用一个空格分开(split)数字和文本后,只有第一部分是有用的。由于数字里有逗号,可以用正则表达式过滤掉任何不是数字的字符。

为了完成爬虫,下面的函数调用了之前提到的所有代码:

def show_video_stats():
  video_page_urls = get_video_page_urls()
  for video_page_url in video_page_urls:
    print get_video_data(video_page_url)

3.3并行处理
上面到目前为止的脚本工作地很好,但是有一百多个视频它就要跑个一会儿了。事实上我们没做什么工作,大部分时间都浪费在了下载页面上,在这段时间脚本时被阻塞的。如果脚本能同时跑多个下载任务,可能就会更高效了,是吗?

回顾当时写一篇使用 Node.js 的爬虫文章的时候,并发性是伴随 JavaScript 的异步特性自带来的。使用 Python 也能做到,不过需要显示地指定一下。像这个例子,我将开启一个拥有8个可并行化进程的进程池。代码出人意料的简洁:

from multiprocessing import Pool

def show_video_stats(options):
  pool = Pool(8)
  video_page_urls = get_video_page_urls()
  results = pool.map(get_video_data, video_page_urls)

multiprocessing.Pool 类开启了8个工作进程等待分配任务运行。为什么是8个?这是我电脑上核数的两倍。当时实验不同大小的进程池时,我发现这是最佳的大小。小于8个使脚本跑的太慢,多于8个也不会让它更快。

调用pool.map()类似于调用常规的map(),它将会对第二个参数指定的迭代变量中的每个元素调用一次第一个参数指定的函数。最大的不同是,它将发送这些给进程池所拥有的进程运行,所以在这个例子中八个任务将会并行运行。

节省下来的时间是相当大的。在我的电脑上,第一个版本的脚本用了75秒完成,然而进程池的版本做了同样的工作只用了16秒!

3.4完成爬虫脚本
我最终版本的爬虫脚本在获得数据后还做了更多的事情。

我添加了一个--sort命令行参数去指定一个排序标准,可以指定views,likes或者dislikes。脚本将会根据指定属性对结果数组进行递减排序。另一个参数,--max代表了要显示的结果数的个数,万一你只想看排名靠前的几条而已。最后,我还添加了一个--csv选项,为了可以轻松地将数据导到电子制表软件中,可以指定数据以 CSV 格式打印出来,而不是表对齐格式。

完整脚本显示在下方:

import argparse
import re
from multiprocessing import Pool
import requests
import bs4

root_url = 'http://pyvideo.org'
index_url = root_url + '/category/50/pycon-us-2014'

def get_video_page_urls():
  response = requests.get(index_url)
  soup = bs4.BeautifulSoup(response.text)
  return [a.attrs.get('href') for a in soup.select('div.video-summary-data a[href^=/video]')]

def get_video_data(video_page_url):
  video_data = {}
  response = requests.get(root_url + video_page_url)
  soup = bs4.BeautifulSoup(response.text)
  video_data['title'] = soup.select('div#videobox h3')[0].get_text()
  video_data['speakers'] = [a.get_text() for a in soup.select('div#sidebar a[href^=/speaker]')]
  video_data['youtube_url'] = soup.select('div#sidebar a[href^=http://www.youtube.com]')[0].get_text()
  response = requests.get(video_data['youtube_url'])
  soup = bs4.BeautifulSoup(response.text)
  video_data['views'] = int(re.sub('[^0-9]', '',
                   soup.select('.watch-view-count')[0].get_text().split()[0]))
  video_data['likes'] = int(re.sub('[^0-9]', '',
                   soup.select('.likes-count')[0].get_text().split()[0]))
  video_data['dislikes'] = int(re.sub('[^0-9]', '',
                    soup.select('.dislikes-count')[0].get_text().split()[0]))
  return video_data

def parse_args():
  parser = argparse.ArgumentParser(description='Show PyCon 2014 video statistics.')
  parser.add_argument('--sort', metavar='FIELD', choices=['views', 'likes', 'dislikes'],
            default='views',
            help='sort by the specified field. Options are views, likes and dislikes.')
  parser.add_argument('--max', metavar='MAX', type=int, help='show the top MAX entries only.')
  parser.add_argument('--csv', action='store_true', default=False,
            help='output the data in CSV format.')
  parser.add_argument('--workers', type=int, default=8,
            help='number of workers to use, 8 by default.')
  return parser.parse_args()

def show_video_stats(options):
  pool = Pool(options.workers)
  video_page_urls = get_video_page_urls()
  results = sorted(pool.map(get_video_data, video_page_urls), key=lambda video: video[options.sort],
           reverse=True)
  max = options.max
  if max is None or max > len(results):
    max = len(results)
  if options.csv:
    print(u'"title","speakers", "views","likes","dislikes"')
  else:
    print(u'Views +1 -1 Title (Speakers)')
  for i in range(max):
    if options.csv:
      print(u'"{0}","{1}",{2},{3},{4}'.format(
        results[i]['title'], ', '.join(results[i]['speakers']), results[i]['views'],
        results[i]['likes'], results[i]['dislikes']))
    else:
      print(u'{0:5d} {1:3d} {2:3d} {3} ({4})'.format(
        results[i]['views'], results[i]['likes'], results[i]['dislikes'], results[i]['title'],
        ', '.join(results[i]['speakers'])))

if __name__ == '__main__':
  show_video_stats(parse_args())

下方输出的是在我写完代码时前25个观看数最多的会议:

(venv) $ python pycon-scraper.py --sort views --max 25 --workers 8
Views +1 -1 Title (Speakers)
 3002 27  0 Keynote - Guido Van Rossum (Guido Van Rossum)
 2564 21  0 Computer science fundamentals for self-taught programmers (Justin Abrahms)
 2369 17  0 Ansible - Python-Powered Radically Simple IT Automation (Michael Dehaan)
 2165 27  6 Analyzing Rap Lyrics with Python (Julie Lavoie)
 2158 24  3 Exploring Machine Learning with Scikit-learn (Jake Vanderplas, Olivier Grisel)
 2065 13  0 Fast Python, Slow Python (Alex Gaynor)
 2024 24  0 Getting Started with Django, a crash course (Kenneth Love)
 1986 47  0 It's Dangerous to Go Alone: Battling the Invisible Monsters in Tech (Julie Pagano)
 1843 24  0 Discovering Python (David Beazley)
 1672 22  0 All Your Ducks In A Row: Data Structures in the Standard Library and Beyond (Brandon Rhodes)
 1558 17  1 Keynote - Fernando Pérez (Fernando Pérez)
 1449  6  0 Descriptors and Metaclasses - Understanding and Using Python's More Advanced Features (Mike Müller)
 1402 12  0 Flask by Example (Miguel Grinberg)
 1342  6  0 Python Epiphanies (Stuart Williams)
 1219  5  0 0 to 00111100 with web2py (G. Clifford Williams)
 1169 18  0 Cheap Helicopters In My Living Room (Ned Jackson Lovely)
 1146 11  0 IPython in depth: high productivity interactive and parallel python (Fernando Perez)
 1127  5  0 2D/3D graphics with Python on mobile platforms (Niko Skrypnik)
 1081  8  0 Generators: The Final Frontier (David Beazley)
 1067 12  0 Designing Poetic APIs (Erik Rose)
 1064  6  0 Keynote - John Perry Barlow (John Perry Barlow)
 1029 10  0 What Is Async, How Does It Work, And When Should I Use It? (A. Jesse Jiryu Davis)
 981 11  0 The Sorry State of SSL (Hynek Schlawack)
 961 12  2 Farewell and Welcome Home: Python in Two Genders (Naomi Ceder)
 958  6  0 Getting Started Testing (Ned Batchelder)
Python 相关文章推荐
Python中的赋值、浅拷贝、深拷贝介绍
Mar 09 Python
举例讲解Python中的身份运算符的使用方法
Oct 13 Python
python动态加载包的方法小结
Apr 18 Python
Python多线程中阻塞(join)与锁(Lock)使用误区解析
Apr 27 Python
Python实现确认字符串是否包含指定字符串的实例
May 02 Python
Python实现将HTML转成PDF的方法分析
May 04 Python
pandas条件组合筛选和按范围筛选的示例代码
Aug 26 Python
用openCV和Python 实现图片对比,并标识出不同点的方式
Dec 19 Python
pytorch-神经网络拟合曲线实例
Jan 15 Python
新手常见Python错误及异常解决处理方案
Jun 18 Python
Python docutils文档编译过程方法解析
Jun 23 Python
Python OpenCV去除字母后面的杂线操作
Jul 05 Python
使用Python的urllib和urllib2模块制作爬虫的实例教程
Jan 20 #Python
使用python实现省市三级菜单效果
Jan 20 #Python
八大排序算法的Python实现
Jan 28 #Python
详解C++编程中一元运算符的重载
Jan 19 #Python
Python中使用Queue和Condition进行线程同步的方法
Jan 19 #Python
简单总结Python中序列与字典的相同和不同之处
Jan 19 #Python
举例讲解如何在Python编程中进行迭代和遍历
Jan 19 #Python
You might like
Mysql的常用命令
2006/10/09 PHP
PHP 正则表达式之正则处理函数小结(preg_match,preg_match_all,preg_replace,preg_split)
2012/10/05 PHP
PHP Curl出现403错误的解决办法
2014/05/29 PHP
php while循环控制的简单实例
2016/05/30 PHP
浅谈PHP中的数据传输CURL
2016/09/06 PHP
PHP时间戳和日期相互转换操作实例小结
2018/12/18 PHP
Ext javascript建立超链接,进行事件处理的实现方法
2009/03/22 Javascript
jQuery lazyload 的重复加载错误以及修复方法
2010/11/19 Javascript
jQuery.query.js 取参数的两点问题分析
2012/08/06 Javascript
iframe异步加载实现点击左边菜单加载右边内容实例讲解
2013/03/04 Javascript
javascript full screen 全屏显示页面元素的方法
2013/09/27 Javascript
jquery trigger伪造a标签的click事件取代window.open方法
2014/06/23 Javascript
node.js中的fs.link方法使用说明
2014/12/15 Javascript
Javascript节点关系实例分析
2015/05/15 Javascript
NodeJS遍历文件生产文件列表功能示例
2017/01/22 NodeJs
浅谈react.js中实现tab吸顶效果的问题
2017/09/06 Javascript
微信小程序—setTimeOut定时器的问题及解决
2019/07/26 Javascript
在Vue环境下利用worker运行interval计时器的步骤
2019/08/01 Javascript
JS Html转义和反转义(html编码和解码)的实现与使用方法总结
2020/03/10 Javascript
vue和小程序项目中使用iconfont的方法
2020/05/19 Javascript
javascript实现移动端上传图片功能
2020/08/18 Javascript
python实现模拟按键,自动翻页看u17漫画
2015/03/17 Python
python实现的多线程端口扫描功能示例
2017/01/21 Python
python itchat实现微信自动回复的示例代码
2017/08/14 Python
Python面向对象编程之继承与多态详解
2018/01/16 Python
python3实现名片管理系统
2020/11/29 Python
Python实现查找二叉搜索树第k大的节点功能示例
2019/01/24 Python
Python range、enumerate和zip函数用法详解
2019/09/11 Python
python基于gevent实现并发下载器代码实例
2019/11/01 Python
Python partial函数原理及用法解析
2019/12/11 Python
Python爬取腾讯视频评论的思路详解
2019/12/19 Python
个人简历自我评价
2014/02/02 职场文书
2014年店长工作总结
2014/11/17 职场文书
2014年政工师工作总结
2014/12/18 职场文书
Python selenium模拟网页点击爬虫交管12123违章数据
2021/05/26 Python
js不常见操作运算符总结
2021/11/20 Javascript