爬虫实践爬取官方新闻标题正文时间

Posted zstar-_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫实践爬取官方新闻标题正文时间相关的知识,希望对你有一定的参考价值。

爬虫用的频率较少,每次使用都会手生,特此记录一次实战经历。

项目需求

要求爬取济南市政务网中“滚动预警”菜单中的文章,包括文章标题,文章正文,文章时间,并保存为txt文件。

项目分析

1、判断可爬取的内容

首先查看该网站的robots.txt文件,发现并不存在该文件。因此相关公开信息可正常爬取。

2、确定网页的加载模式

网页加载可分为静态加载和动态加载。
在网页中右键->选择查看源代码,即网页的静态代码。在网页中右键->检查,可查看浏览器当前渲染的内容。
若两者一致,则网页是静态加载。此时,通常使用requests.get的方式即可获取到网页数据。
若两者不一致,网页是动态加载。此时需通过开发者后台查看本地向服务器发送的交互性数据(XHR)。

每隔3页,网页会短暂卡顿,进行加载,同时可以发现多了一份XHR数据,如图所示,此时,请求的网址如上所示,url中标记了起始数据和结束数据,同时,网页的请求方法为POST。

3、查看提交的表单内容


如图所示,提交的表单主要有七个数据,查看该网站的其他页面,可以大概猜测:
webid用于区分不同的大板块,columnid用于区分每个大板块中的小板块,其它属性未知。在翻页过程中,仅有url发生变化,提交的表单内容固定。
还可以发现,应急要闻和滑动预警所请求的url相同,区别是表单数据:应急要闻的columnid为29112,滑动预警的columnid为34053。

4、获取文章标题、内容与发布时间

通过上面的分析,已经可以通过post的方式获取各页目录的源代码,再次基础上,需要通过目录的链接,进入到每篇文章的页面进行,标题、正文、时间的提取。
通过bs4的函数工具以及正则表达式,可将链接内容提取出来,存放到Linklist。
点击链接进行跳转,可以发现正文内容页面使用静态加载,此时使用get或post的方法均可得到文章内容。我在这里仍沿用了之前封装好的post方法。
分别提取文章标题、内容、时间存储至title_list、content_list、time_list。

5、寻找规律自动翻页

通过上面的操作,已经可以获取了一次加载的内容,即三页内容(27条新闻),下面将通过寻找规律进行多次加载。
寻找规律:
1-3页:
http://jnsafety.jinan.gov.cn/module/web/jpage/dataproxy.jsp?startrecord=1&endrecord=27&perpage=9
4-6页:
http://jnsafety.jinan.gov.cn/module/web/jpage/dataproxy.jsp?startrecord=28&endrecord=54&perpage=9
255页(最后一页):
http://jnsafety.jinan.gov.cn/module/web/jpage/dataproxy.jsp?startrecord=1999&endrecord=2025&perpage=9
发现仅是变化了startrecord(开始页面)和endrecord(结束页面)
于是设定开始页面为i=1,结束页面为i+26,每次遍历i+27,直到返回的Linklist为空,跳出循环。

完整代码

import os
from bs4 import BeautifulSoup
import re
import requests


# post得到网页并用bs4进行网页解析
def gethtml(url):
    header = 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) '
                            'Chrome/52.0.2743.116 Safari/537.36'
    data = 
        'col': 1,
        'webid': '33',
        'path': 'http://jnsafety.jinan.gov.cn/',
        # 'columnid': '29112',  # 29112对应应急要闻
        'columnid': '34053',  # 34053对应滚动预警
        'sourceContentType': '1',
        'unitid': '92715',
        'webname': '%E6%B5%8E%E5%8D%97%E5%B8%82%E5%BA%94%E6%80%A5%E7%AE%A1%E7%90%86%E5%B1%80',
        'permissiontype': 0
    
    rep = requests.post(url=url, data=data, headers=header, stream=True).text
    # 解析网页
    soup = BeautifulSoup(rep, 'html.parser')
    return soup


# 从a标签中切分出具体文章链接
def split_link(string):
    start_string = 'http'
    end_string = '.html'
    sub_str = ""
    start = string.find(start_string)
    # 只要start不等于-1,说明找到了http
    while start != -1:
        # 找结束的位置
        end = string.find(end_string, start)
        # 截取字符串 结束位置=结束字符串的开始位置+结束字符串的长度
        sub_str = string[start:end + len(end_string)]
        # 找下一个开始的位置
        # 如果没有下一个开始的位置,结束循环
        start = string.find(start_string, end)
    return sub_str


# 截取文章发布时间的年月日
def split_time(t):
    year = t[0:4]
    month = t[5:7]
    day = t[8:10]
    data = "%s-%s-%s" % (year, month, day)
    return data


# 获取一页中的所有链接
def get_link_list(soup):
    # 使用正则表达式提取链接内容
    p = re.compile(r'<div class="ajj_clear_box">(.*?)</div>?', re.S)
    items = re.findall(p, str(soup))
    # print(items)
    Linklist = []
    # 返回出各网站内容链接
    for item in items:
        # print(item)
        link = split_link(item)
        Linklist.append(link)
    return Linklist


# 获取单篇文章标题、内容与发布时间
def get_title_content(soup_ev):
    # 文章标题
    title = soup_ev.find(name="meta", attrs="name": "ArticleTitle")['content']
    # print(title)
    # 文章内容
    content = soup_ev.find(name="div", attrs="id": "zoom").findAll(name="span")
    # 文章发布时间
    pub_time = soup_ev.find(name="meta", attrs="name": "pubdate")['content']
    p_time = split_time(pub_time)
    # print(p_time)
    return title, content, p_time


# 保存单篇新闻
def save_content(title, content, index, time):
    for item in content:
        text_content = item.text
        # print(text_content)
        # 以标题名作为文件名,防止某些标题含有特殊符号,将其替换为空
        sets = ['/', '\\\\', ':', '*', '?', '"', '<', '>', '|']
        for char in title:
            if char in sets:
                title = title.replace(char, '')
        tex_name = "%d%s-%s" % (index, title, time)
        # 注:由于每段文字是分离的,因此写入文件模式设定为追加写入(a)
        # 文件夹在主函数内创建
        with open(r'./应急要闻/%s.txt' % tex_name, mode='a', encoding='utf-8') as f:
            # 每段文字进行换行
            f.write(text_content + "\\n")
        ''' 滚动预警
        with open(r'./滚动预警/%s.txt' % tex_name, mode='a', encoding='utf-8') as f:
            # 每段文字进行换行
            f.write(text_content + "\\n")
        '''

# 获取一次加载的新闻链接列表
def get_news_list(Linklist):
    title_list = []
    content_list = []
    time_list = []
    for item in Linklist:
        # item、soup_ev都有可能因返回数据出现异常中断,这里对异常数据不作处理,跳过中断
        try:
            soup_ev = getHtml(item)
            title, content, p_time = get_title_content(soup_ev)
            title_list.append(title)
            content_list.append(content)
            time_list.append(p_time)
        except Exception:
            pass
        continue
    return title_list, content_list, time_list


# 根据文章的时间重新进行排序(按时间从后到前)
def sort_news(title_list, content_list, time_list):
    title_content_time = zip(title_list, content_list, time_list)
    sorted_title_content_time = sorted(title_content_time, key=lambda x: x[2], reverse=True)
    result = zip(*sorted_title_content_time)
    title_list, content_list, time_list = [list(x) for x in result]
    return title_list, content_list, time_list


# 保存list中所有新闻
def save_all(title_list, content_list, time_list):
    loop = zip(title_list, content_list, time_list)
    index = 1
    for title, content, time in loop:
        save_content(title, content, index, time)
        index += 1


if __name__ == '__main__':
    # 在当前目录下创建存储新闻内容的文件夹
    path = os.getcwd()
    file_path = path + '\\\\' + str("滚动预警")
    # file_path = path + '\\\\' + str("应急要闻")
    os.mkdir(file_path)
    # 存储每三页的标题、内容、时间
    title_list = []
    content_list = []
    time_list = []
    # 存储所有新闻的标题、内容、时间
    tol_title_list = []
    tol_content_list = []
    tol_time_list = []
    i = 1
    while True:
        url = 'http://jnsafety.jinan.gov.cn/module/web/jpage/dataproxy.jsp?startrecord=%d&endrecord=%d&perpage=9' % (i, i + 26)
        soup = getHtml(url)
        Linklist = get_link_list(soup)
        # 取消下面的注释,可打印出每次请求得到的链接数,以显示程序正在允许中
        # print(len(Linklist))
        # print(Linklist)
        # 假如爬完所有内容,跳出循环
        if Linklist:
            title_list, content_list, time_list = get_news_list(Linklist)
            tol_title_list.extend(title_list)
            tol_content_list.extend(content_list)
            tol_time_list.extend(time_list)
        else:
            break
        i = i + 27
    # print(len(tol_title_list))
    # print(len(tol_content_list))
    # print(len(tol_time_list))
    tol_title_list, tol_content_list, tol_time_list = sort_news(tol_title_list, tol_content_list, tol_time_list)
    save_all(tol_title_list, tol_content_list, tol_time_list)

常见报错

1、(‘Connection aborted.’, TimeoutError(10060, ‘由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。’, None, 10060, None))

解决方式:关闭电脑的防火墙。

2、Failed to establish a new connection: [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。’))

问题分析:该报错可能是ip被封或者爬虫访问速度过快,服务器来不及响应。
解决方式:每次gethtml添加time.sleep(1),让每次爬取间隔1秒时间,若仍报错,尝试使用代理ip。

以上是关于爬虫实践爬取官方新闻标题正文时间的主要内容,如果未能解决你的问题,请参考以下文章

java网络爬虫爬取百度新闻

爬虫智能解析库 Readability 和 Newspaper 的用法

官方数据网站可以使用爬虫软件吗

爬虫笔记第一次写爬虫,爬取新浪新闻网标题

python2.7 爬虫初体验爬取新浪国内新闻_20161130

网络爬虫百度新闻标题及链接爬取