以视频爬取实例讲解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中使用Neo4j数据库的教程
Apr 16 Python
python使用Paramiko模块实现远程文件拷贝
Apr 30 Python
python项目对接钉钉SDK的实现
Jul 15 Python
Python使用scrapy爬取阳光热线问政平台过程解析
Aug 14 Python
python实现KNN分类算法
Oct 16 Python
Python基础之字符串操作常用函数集合
Feb 09 Python
关于Tensorflow 模型持久化详解
Feb 12 Python
python虚拟环境模块venv使用及示例
Mar 04 Python
selenium+超级鹰实现模拟登录12306
Jan 24 Python
Python实现曲线拟合的最小二乘法
Feb 19 Python
python爬取youtube视频的示例代码
Mar 03 Python
撤回我也能看到!教你用Python制作微信防撤回脚本
Jun 11 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
PHP 使用 Imagick 裁切/生成缩略图/添加水印自动检测和处理 GIF
2016/02/19 PHP
php通过文件头判断格式的方法
2016/05/28 PHP
在视频前插入广告
2006/11/20 Javascript
初探nodeJS
2017/01/24 NodeJs
jQuery extend()详解及简单实例
2017/05/06 jQuery
基于JavaScript实现选项卡效果
2017/07/21 Javascript
SVG动画vivus.js库使用小结(实例代码)
2017/09/14 Javascript
jquery 获取索引值在一定范围的列表方法
2018/01/25 jQuery
Vue header组件开发详解
2018/01/26 Javascript
JavaScript指定断点操作实例教程
2018/09/18 Javascript
vue 双向数据绑定的实现学习之监听器的实现方法
2018/11/30 Javascript
详解js 创建对象的几种方法
2019/03/08 Javascript
JS如何生成动态列表
2020/09/22 Javascript
由Python运算π的值深入Python中科学计算的实现
2015/04/17 Python
Python 使用requests模块发送GET和POST请求的实现代码
2016/09/21 Python
Python3 pip3 list 出现 DEPRECATION 警告的解决方法
2019/02/16 Python
Django框架模型简单介绍与使用分析
2019/07/18 Python
Python学习笔记之错误和异常及访问错误消息详解
2019/08/08 Python
python+selenium 鼠标事件操作方法
2019/08/24 Python
opencv3/Python 稠密光流calcOpticalFlowFarneback详解
2019/12/11 Python
Python如何转换字符串大小写
2020/06/04 Python
Python调用REST API接口的几种方式汇总
2020/10/19 Python
如何在 Matplotlib 中更改绘图背景的实现
2020/11/26 Python
关于HTML5的安全问题开发人员需要牢记的
2012/06/21 HTML / CSS
Derek Rose官网:英国高档睡衣、家居服和内衣品牌
2020/01/18 全球购物
关键字throw与throws的用法差异
2016/11/22 面试题
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
抗震救灾标语
2014/06/26 职场文书
农村党建工作汇报材料
2014/10/27 职场文书
女性健康知识讲座通知
2015/04/23 职场文书
心灵点滴观后感
2015/06/02 职场文书
2016春季运动会通讯稿
2015/07/18 职场文书
运动会5000米加油稿
2015/07/21 职场文书
送给小学生的暑假礼物!小学生必背99首古诗
2019/07/02 职场文书
如何利用JavaScript实现二叉搜索树
2021/04/02 Javascript
InterProcessMutex实现zookeeper分布式锁原理
2022/03/21 Java/Android