Python爬虫实战入门三:简单的HTML解析——爬取腾讯新闻

上一章咱们使用Python实现了一个简单的HTTP请求,瞧着简单,爬虫就是模拟人打开一个个URL浏览一个个网页来爬取数据的,一个成功的HTTP请求,就是一个爬虫的基础。
接下来,咱们以一个实际的例子:爬取百度新闻,来介绍使用BeautifulSoup对HTML进行解析处理。

爬取腾讯新闻

1、寻找数据特征
腾讯新闻的网址URL为:http://news.qq.com/
我们打开网页看看:

腾讯新闻

我们需要爬取这个页面每一条新闻的标题,鼠标右击一条新闻的标题,选择“审查元素”,出现下图的窗口:

审查元素

图片中红框的位置就是那一条新闻标题在HTML中的结构、位置和表现形式:

<a target="_blank" class="linkto" href="http://news.qq.com/a/20170104/001280.htm">北京2016年39天重污染 PM2.5仍超国标一倍</a>

它上一级元素为:<em class=”f14 l24″>,再上一级元素为:<div class=”text”>
我们再看另一条新闻的标题,发现它的结构和之前我们分析的新闻标题的结构是一样的:

<div class="text">
<em class="f14 l24">
<a target="_blank" class="linkto" href="http://news.qq.com/a/20170104/000968.htm">台拟将蔡英文贺岁春联“自自冉冉”一词列入辞典
</a>
</em>
</div>

通过这些信息,我们就可以确定新闻标题在HTML文档中的位置。
接下来,我们开始使用Python对腾讯新闻标题进行爬取
** 2、编写爬取代码
首先上完整的代码

# coding:utf-8

# 引入相关模块
import requests
from bs4 import BeautifulSoup

url = "http://news.qq.com/"
# 请求腾讯新闻的URL,获取其text文本
wbdata = requests.get(url).text
# 对获取到的文本进行解析
soup = BeautifulSoup(wbdata,'lxml')
# 从解析文件中通过select选择器定位指定的元素,返回一个列表
news_titles = soup.select("div.text > em.f14 > a.linkto")

# 对返回的列表进行遍历
for n in news_titles:
    # 提取出标题和链接信息
    title = n.get_text()
    link = n.get("href")
    data = {
        '标题':title,
        '链接':link
    }
    print(data)

 

运行程序,获取到的部分结果为如下所示:

C:\Python34\python.exe H:/python/scrap/scrap_tech_book/scrap_tennetNews.py
{'标题': '2017年首个断崖降级官员曝光 他犯了什么事?', '链接': 'http://news.qq.com/a/20170104/002209.htm'}
{'标题': '湄公河武装执法纪实:队员吃饭都要死死盯着前方', '链接': 'http://news.qq.com/a/20170104/007148.htm'}
{'标题': '河南台前县出台彩礼“指导标准”:总数不得高于6万', '链接': 'http://news.qq.com/a/20170104/001693.htm'}
{'标题': '摆射击摊获刑老太谈上诉原因:怕预订的咸菜浪费了', '链接': 'http://news.qq.com/a/20170104/013261.htm'}
{'标题': '美国联军承认在叙伊有“误伤”:打死近二百平民', '链接': 'http://news.qq.com/a/20170104/005283.htm'}
{'标题': '辽宁黑山企业人员退休先缴四百元:未经省级审批', '链接': 'http://news.qq.com/a/20170104/006323.htm'}
{'标题': '罗布泊遇难者李中华的骨灰回家:身份仍有3大谜团', '链接': 'http://news.qq.com/a/20170104/003139.htm'}
{'标题': '嫌楼上邻居吵,这家人买“震楼神器”反击', '链接': 'http://news.qq.com/a/20170104/001582.htm'}
{'标题': '12岁女孩疑似被弃 因妈妈买不起她的火车票?', '链接': 'http://news.qq.com/a/20170104/004622.htm'}
{'标题': '向美国商务部说“NO” 这家中国民企打赢美国官司', '链接': 'http://news.qq.com/a/20170104/000322.htm'}
{'标题': '台拟将蔡英文贺岁春联“自自冉冉”一词列入辞典', '链接': 'http://news.qq.com/a/20170104/000968.htm'}
{'标题': '新华社:姚明资历经验尚浅 任篮协主席尚有障碍', '链接': 'http://news.qq.com/a/20170104/000330.htm'}
{'标题': '新京报:法院拍卖BB弹玩具枪,自己人也糊涂?', '链接': 'http://news.qq.com/a/20170104/001344.htm'}

这正是我们所需要的。
虽然代码很简单,但还是做一点点讲解,方便刚刚接触的同学。
3、代码解析

# coding:utf-8

首先,我们定义了文件的编码形式为UTF-8,以避免一些编码错误导致中文乱码。

import requests
from bs4 import BeautifulSoup

然后,我们引入了相关的模块,requests用于HTTP请求,BeautifulSoup用于解析HTML响应。

url = "http://news.qq.com/"

设置一个变量url,值为腾讯新闻的URL

wbdata = requests.get(url).text

使用requests.get()对URL发起GET方式的HTTP请求,并使用text()方法获取响应的文本内容,最后将其赋值给变量wbdata。

soup = BeautifulSoup(wbdata,'lxml')

使用BeautifulSoup对响应文本wbdata进行解析处理,这里使用的是lxml库,如何没有安装,可以使用Python自带的html.parser,效果也是一样的。

news_titles = soup.select("div.text > em.f14 > a.linkto")

在解析后的文本中,使用select选择器,在文本中选择指定的元素,通常我们还会使用find()和findall()方法来进行元素选择。
这一步返回的为一个列表,列表内的元素为匹配的元素的HTML源码。

for n in news_titles:   
 # 提取出标题和链接信息    
  title = n.get_text()    
  link = n.get("href")    
  data = {'标题':title,'链接':link}    
  print(data)

对结果列表进行遍历,再从遍历的元素中提取出数据,get(“href”)表示获取属性名为“href”的属性值,get_text()表示获取标签的文本信息。

这样,一个简单的腾讯新闻爬虫就完成了,如果对requests模块和BeautifulSoup模块有更加深的学习欲望,可以查看它们的官方文档:
requests官方文档(中文): http://docs.python-requests.org/zh_CN/latest/
BeautifulSoup文档(中文):
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

猜你也喜欢

  1. Plum说道:

    博主你第一段里写成百度新闻了 :eek:

    1. zmister说道:

      失误失误,开始是选百度新闻的,后来换成爬腾讯

  2. Leo-Chen说道:

    代码第十三行的选择器中的div.text > em.f14 > a.linkto怎么得到,是不是谷歌浏览器审查copy selector,我之前看到教程也是这样,但我自己copy出来的就是“body > div.wrap > div.container > div.main > div.news > div.con > div.item.channel > div:nth-child(1) > div.content > em > a”这么个奇怪的东东,而且直接抄博主的里的div.text > em.f14 > a.linkto也会报错,这是为什么,我几乎完全小白,希望博主解答

    1. Leo-Chen说道:

      没错了,照博主代码写可以出结果,但就是还是不懂那个选择器里的div.text > em.f14 > a.linkto是怎么出来的

      1. zmister说道:

        div.text > em.f14 > a.linkto表示

        下面的下面的
    2. zmister说道:

      额,这个你需要了解一下CSS选择器的基本的概念,从调试控制台copy出来的是其对于整个页面的CSS选择器位置,我设置的那个是提取简化之后的,要看具体页面来,>符号表示html元素的下一层级关系

    3. MJ说道:

      我是python3.5:"div.text > em.f14 > a.linkto"这一块,“>”两边需要有空格.
      W3C文档中讲的是“”CSS选择器子结合符两边可以有空白符,这是可选的”

  3. 无怀氏说道:

    赞赞!

  4. LEO说道:

    为什么我爬到的都是{u'\u94fe\u63a5': 'http://news.qq.com/a/20170322/032748.htm', u'\u6807\u9898': u'\u4e2d\u65b9\u56de\u5e94\u671d\u9c9c\u53d1\u5c04\u6570\u679a\u5bfc\u5f39\uff1a\u534a\u5c9b\u5c40\u52bf\u5251\u62d4\u5f29\u5f20'} 的

    1. zmister说道:

      这个你用utf-8编码解码一下就好了

      1. 匿名说道:

        怎么解码,我的也是 {u'\u94fe\u63a5': 'http://news.qq.com/a/20170322/032748.htm', u'\u6807\u9898': u'\u4e2d\u65b9\u56de\u5e94\u671d\u9c9c\u53d1\u5c04\u6570\u679a\u5bfc\u5f39\uff1a\u534a\u5c9b\u5c40\u52bf\u5251\u62d4\u5f29\u5f20'}

  5. 杯具也悲剧说道:

    参考这篇加百度正则爬了一个网站的动漫图,谢谢指导

  6. kaze说道:

    为什么我跑出来会报错,提示:
    bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?

  7. kaze说道:

    博主,“工具准备”篇中把安装lxml库加上吧,实测得先pip install lxml,不然按照本文方法写的代码跑不了

    1. zmister说道:

      哈哈,失误失误,多谢指正

  8. 菜鸟一枚说道:

    请问各位大师,这个程序为什么没有反应?还望赐教指点,小弟拜谢
    >>> # coding:utf-8
    ... import requests
    >>> from bs4 import BeautifulSoup
    >>>
    >>> url='http://new.qq.com/'
    >>> wbdata=requests.get(url).text
    >>> soup=BeautifulSoup(wbdata,'lxml')
    >>> news_titles=soup.select('div.text>em.f14>a.linkto')
    >>>
    >>> for n in news_titles:
    ... title=n.get_text()
    ... link=n.get('href')
    ... data={
    ... '标题':title,
    ... '链接':link
    ... }
    ... print(data)
    ...

    1. zmister说道:

      css选择器的>之间要有空格,不是div.text>em.f14>a.linkto而是div.text > em.f14 > a.linkto

      1. 未详吹牛中说道:

        感谢!

  9. aa说道:

    老师 像您站上的 a 是没有class 怎么选呢

    1. zmister说道:

      那就用其他的定位方式,比如xpath、与正则结合等等

  10. 匿名说道:

    为什么会出现 AttributeError: module 'requests' has no attribute 'get'
    出不来结果 :cry: :cry: :cry:

  11. ruanruan说道:

    出现报错,运行不下去怎么办 :cry: :cry: :cry:
    AttributeError: module 'requests' has no attribute 'get'

    1. zmister说道:

      模块有没有被赋值

  12. FIGO说道:

    写的真好,感谢

  13. 正一说道:

    我修改了被爬的对象,然后标题就乱码了。请帮我判断一下是怎么回事^^
    url改成:url = 'http://www.cyzone.cn/'
    定位改成:news_titles = soup.select('div.item-intro > a.item-title')

    现象:'标题': 'ã\x80\x8aæ\x88\x91ç\x9a\x84ä¸\x96ç\x95\x8cã\x80\x8bä¸\xadå\x9b½ç\x89\x88å¼\x80大ä¼\x9aï¼\x9a请尽æ\x83\x85ç\x8e©è\x80\x8då\x88\x9bé\x80\xa0ï¼\x8cç\x84¶å\x90\x8eå\x88\x86é\x92±', '链接': 'http://www.cyzone.cn/a/20180202/323770.html'

    1. zmister说道:

      网页编码问题

  14. FIGO说道:

    Traceback (most recent call last):
    File "D:\PythonSpace\gg.py", line 7, in
    soup_data=BeautifulSoup(data,'lxml')
    File "D:\Python33\lib\site-packages\bs4\__init__.py", line 192, in __init__
    elif len(markup) <= 256 and (
    TypeError: object of type 'Response' has no len()

    跟原文代码一样,只是url不同,返回了这样报错,请问大神是什么原因

    1. zmister说道:

      URL不同,可能页面结构也就不一样了。理解各个步骤的含义,requests.get相当于打开一个网页,BeautifulSoup则相当于从呈现的网页中复制一些文本。

      1. 匿名说道:

        那这种问题如何解决啊,不同的url可能出现这个错误

  15. 匿名说道:

    老师,你是一个什么样的好人!写这么好的东西……
    顺便请教一下,for循环中的n.get中的n是什么

  16. 匿名说道:

    老师,你是一个什么样的好人!写这么好的东西……
    顺便请教一下,for循环中的n.get中的n是什么
    NameError: name 'n' is not found!

  17. 毕设妹说道:

    老师,我今天照着你的代码爬取腾讯新闻,但是得到的结果只有一个标题和链接({'标题': '黄毅清自称年轻不懂事,为女儿愿向黄奕低头求复合', '链接': 'http://new.qq.com/omn/20181213A1CS3N.html'})。输出new_titles得到的是[“俄罗斯女特工”在美认罪:影响特朗普对俄政策, 法国检察部门:警方已将斯特拉斯堡枪案嫌犯击毙,......],有人和我说转化为json可以,但我不会,请问一下这样怎么解决???谢谢

    1. 州的先生说道:

      不需要转json,你对new_titles使用for循环进行遍历即可。

  18. fengzi说道:

    现在应该爬不了了吧,我换个网站可以爬。

    1. 州的先生说道:

      嗯,知道原理就可以

    2. 乔木说道:

      请问你现在换哪个网站爬的呀

  19. eddy说道:

    hello
    怎么下载print(data)到本地txt file中
    我是这么写的
    fo=open(ubuntu/Desktop/news.txt,'w')
    fo.write(str(data))
    fo.close
    得到的结果只有第一条

发表评论

邮箱地址不会被公开。