以视频爬取实例讲解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 相关文章推荐
使用python3.5仿微软记事本notepad
Jun 15 Python
Python使用QRCode模块生成二维码实例详解
Jun 14 Python
基于python时间处理方法(详解)
Aug 14 Python
Python实现读取txt文件并转换为excel的方法示例
May 17 Python
opencv3/C++图像像素操作详解
Dec 10 Python
python中如何使用insert函数
Jan 09 Python
python 制作python包,封装成可用模块教程
Jul 13 Python
python speech模块的使用方法
Sep 09 Python
Python numpy大矩阵运算内存不足如何解决
Nov 19 Python
python实现socket简单通信的示例代码
Apr 13 Python
Django分页器的用法你都了解吗
May 26 Python
python套接字socket通信
Apr 01 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
《APMServ 5.1.2》使用图解
2006/10/23 PHP
mysql4.1以上版本连接时出现Client does not support authentication protocol问题解决办法
2007/03/15 PHP
复制本贴标题和地址的js代码
2008/07/01 Javascript
jquery判断checkbox(复选框)是否被选中的代码
2010/10/20 Javascript
jquery遍历数组与筛选数组的方法
2013/11/05 Javascript
JQuery 图片滚动轮播示例代码
2014/03/24 Javascript
完美兼容各大浏览器获取HTTP_REFERER方法总结
2014/06/24 Javascript
自定义Angular指令与jQuery实现的Bootstrap风格数据双向绑定的单选与多选下拉框
2015/12/12 Javascript
微信小程序实现图片放大预览功能
2020/10/22 Javascript
vue计算属性时v-for处理数组时遇到的一个bug问题
2018/01/21 Javascript
微信小程序自定义prompt组件步骤详解
2018/06/12 Javascript
解决mpvue + vuex 开发微信小程序vuex辅助函数mapState、mapGetters不可用问题
2018/08/03 Javascript
vue实现微信分享功能
2018/11/28 Javascript
详解JavaScript作用域和作用域链
2019/03/19 Javascript
使用Three.js实现太阳系八大行星的自转公转示例代码
2019/04/09 Javascript
详解Jest结合Vue-test-utils使用的初步实践
2019/06/27 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
vue props default Array或是Object的正确写法说明
2020/07/30 Javascript
解决iView Table组件宽度只变大不变小的问题
2020/11/13 Javascript
[01:01:36]Optic vs paiN 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
Python回调函数用法实例详解
2015/07/02 Python
python使用itchat库实现微信机器人(好友聊天、群聊天)
2018/01/04 Python
PyTorch学习笔记之回归实战
2018/05/28 Python
Python 实现中值滤波、均值滤波的方法
2019/01/09 Python
python实现字符串完美拆分split()的方法
2019/07/16 Python
想学画画?python满足你!
2020/12/24 Python
python中用ggplot绘制画图实例讲解
2021/01/26 Python
毕业生就业推荐信范文
2013/12/01 职场文书
司机检讨书
2014/02/13 职场文书
募捐倡议书
2014/04/14 职场文书
计算机应用专业自荐信
2014/07/05 职场文书
某集团股份有限公司委托书样本
2014/09/24 职场文书
2014党员整改措施思想汇报
2014/10/07 职场文书
2014年个人工作总结报告
2014/11/27 职场文书
公司员工培训管理制度
2015/08/04 职场文书
穷人该怎么创业?谨记以下几点
2019/07/11 职场文书