浅谈天涯社区“工薪一族”爬虫

1. 确定数据结构

首先,明确一个问题:要存什么。

以下是我最终代码的数据结构

{"time": "2022-08-04 10:25:07",  // 开始爬取的时间"pages": 3, // 爬取页面数"posts": [ //大列表,记录各个帖子{"page": 1, //记录以下是哪个页面"posts": [ //列表记录该页帖子{"title": "历史学习记录", //标题"post_time": "2022-08-04 03:37:49", //发送时间"author_id": "潘妮sun", //作者"url": "http://bbs.tianya.cn/post-170-917565-1.shtml", //帖子链接"author_url": "http://www.tianya.cn/112795571", //作者链接"read_num": "8", //阅读数"reply_num": "4", //回复数"content": "黄帝和炎帝其实并不是皇帝,而是古书记载中黄河流域远古..."//帖子内容(文本过长,这里只展示一部分)},......]}]
}

由此可见,我们要存的东西如下:

  • 爬取时间,页数
  • 帖子标题&链接
  • 帖子发送时间
  • 帖子作者&链接
  • 阅读数&回复数
  • 帖子内容

2. 页面分析

2.1 目录页面分析

打开目标页面:http://bbs.tianya.cn/list.jsp?item=170

按下f12,打开开发者工具,分析页面结构。

  1. 主体页面由9个tbody构成,其中第一个为表格标题,其余八个内部各有10个帖子,共80个

  2. 每个tbody内由10个tr构成,记录了帖名和链接、作者和链接、点击量、回复量、最后回复时间

  3. 每页最后会有一个链接指向下一页,如同链表的指针

    这里注意,第一页的下一页按钮是第二个,其余页是第三个

2.2 帖子页面分析

随便打开一条帖子, 如http://bbs.tianya.cn/post-170-878768-1.shtml

按下f12,打开开发者工具,分析页面结构。

  1. html的head标签内有文章题目(后面会提到为啥要说这个)

  2. 发帖时间有两种

    一种为div内单独span标签内,以纯文本形式存储

    另一种为和点击和回复一起整体保存

  3. 帖子内容保存在"bbs-content"的div里,以<br>分段

3. 确定工具

爬取html这里选用request

解析提取html这里选用xpath

文本格式化存储要用到json

记录时间要用到time

提取文本数据可能要用到正则表达式,导入re库(可选)

*注: 这里可以先记录下浏览器的User-Agent, 构造headers

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
}

4. 开始提取

4.1 提取页面

import requests
from lxml import etreeurl = ‘http://bbs.tianya.cn/list.jsp?item=170’
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
}
posts = [] # 保存帖子用
next = ‘’ # 保存下一页链接用raw = requests.get(url, headers=headers) # 爬取页面
html = etree.HTML(raw.text) # 转换为xml给xpath解析
# 取下一页链接
next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/@href')[0]
# 判断第二个是否是下一页按钮,若不是则为第三个按钮
# 第一页以外是a[3]不是a[2](两个条件不能换顺序,否则第一页会报错)
if html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/text()')[0] != '下一页':next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[3]/@href')[0]
tbodys = html.xpath('//*[@id="main"]/div[@class="mt5"]/table/tbody') # 提取9个tbody
tbodys.remove(tbodys[0]) # 移除页首表头(标题 作者  点击  回复  回复时间)
for tbody in tbodys:items = tbody.xpath("./tr")for item in items:title = item.xpath("./td[1]/a/text()")[0].replace('\r', '').replace('\n', '').replace('\t', '') # 帖子题目会有换行符等符号,需要去除post_url = "http://bbs.tianya.cn" + item.xpath("./td[1]/a/@href")[0] # 帖子链接author_id = item.xpath("./td[2]/a/text()")[0] # 作者idauthor_url = item.xpath("./td[2]/a/@href")[0] # 作者链接read_num = item.xpath("./td[3]/text()")[0] # 阅读数reply_num = item.xpath("./td[4]/text()")[0] # 回复数post = {'title': title,'author_id': author_id,'url': post_url,'author_url': author_url,'read_num': read_num,'reply_num': reply_num,}posts.append(post)print(post) # 展示输出结果调试用

4.2 提取单个帖子

post_time = '' # 保存发帖时间
post_content = '' # 保存发帖内容
post_url = ‘http://bbs.tianya.cn/post-170-917511-1.shtml’
postraw = requests.get(posturl, headers=headers)
posthtml = etree.HTML(postraw.text)
# 天涯社区的时间有两种保存格式,这里分别适配
try:posttimeraw = posthtml.xpath('//*[@id="post_head"]/div[2]/div[2]/span[2]/text()')[0] # 发帖时间
except:posttimeraw = posthtml.xpath('//*[@id="container"]/div[2]/div[3]/span[2]/text()[2]')[0] # 发帖时间
# 利用正则进行时间文本格式化 YYYY-MM-DD HH:mm:ss
post_time = re.findall(r'\d+-\d+-\d+ \d+:\d+:\d+', posttimeraw)[0]
if len(title) == 0: # 处理部分因格式特殊取不到标题的帖子title = posthtml.xpath('/html/head/title/text()')[0].replace('_工薪一族_论坛_天涯社区', '')
contents = posthtml.xpath('//*[@id="bd"]/div[4]/div[1]/div/div[2]/div[1]/text()')
# 帖子内容(列表形式,一段一项)
post_content = ''
for string in contents: # 提取正文每一段string = string.replace('\r', '').replace('\n', '').replace('\t', '').replace('\u3000', '') + '\n' # 去除换行符等符号,并加上段间换行符post_content += string # 将每段内容拼接起来

4.3 构造函数

这里的目的是为了拼接单帖和页面代码,实现单页内全部数据的提取(包括题目,内容和数据)

下文为我的实现函数,入参为页面网址urlheaders,出参为构造的单页面所有数据构成的列表posts和下一页的链接next

def get_posts(url, headers):raw = requests.get(url, headers=headers)code = raw.status_codeposts = []next = '' # 加载失败直接返回空,避免报错if code == 200:html = etree.HTML(raw.text)# 取下一页链接next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/@href')[0]# 第一页以外是a[3]不是a[2](两个条件不能换顺序,否则第一页会报错)if html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/text()')[0] != '下一页': # 判断第二个按钮是否是下一页按钮next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[3]/@href')[0] tbodys = html.xpath('//*[@id="main"]/div[@class="mt5"]/table/tbody')tbodys.remove(tbodys[0]) # 移除页首表头(标题   作者  点击  回复  回复时间)for tbody in tbodys:items = tbody.xpath("./tr")for item in items:title = item.xpath("./td[1]/a/text()")[0].replace('\r', '').replace('\n', '').replace('\t', '') # 帖子题目会有换行符等符号,需要去除url = "http://bbs.tianya.cn" + item.xpath("./td[1]/a/@href")[0] # 帖子链接author_id = item.xpath("./td[2]/a/text()")[0] # 作者idauthor_url = item.xpath("./td[2]/a/@href")[0] # 作者链接read_num = item.xpath("./td[3]/text()")[0] # 阅读数reply_num = item.xpath("./td[4]/text()")[0] # 回复数# 获取帖子内容postraw = requests.get(url, headers=headers) postcode = postraw.status_codeif postcode == 200:posthtml = etree.HTML(postraw.text)try:posttimeraw = posthtml.xpath('//*[@id="post_head"]/div[2]/div[2]/span[2]/text()')[0] # 发帖时间except:posttimeraw = posthtml.xpath('//*[@id="container"]/div[2]/div[3]/span[2]/text()[2]')[0] # 发帖时间post_time = re.findall(r'\d+-\d+-\d+ \d+:\d+:\d+', posttimeraw)[0]if len(title) == 0: # 处理部分因格式特殊取不到标题的帖子title = posthtml.xpath('/html/head/title/text()')[0].replace('_工薪一族_论坛_天涯社区', '')contents = posthtml.xpath('//*[@id="bd"]/div[4]/div[1]/div/div[2]/div[1]/text()') # 帖子内容(列表形式,一段一项)post_content = ''for string in contents:string = string.replace('\r', '').replace('\n', '').replace('\t', '').replace('\u3000', '') + '\n' # 去除换行符等符号,并加上段间换行符post_content += string # 将每段内容拼接起来post = {'title': title,'post_time': post_time,'author_id': author_id,'url': url,'author_url': author_url,'read_num': read_num,'reply_num': reply_num,'content': post_content}posts.append(post)print(title) # 输出帖子题目调试用return posts, next

4.4 保存数据

本项目标:构造主函数,实现json格式化保存

def main():url = 'http://bbs.tianya.cn/list.jsp?item=170'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'}postss = {'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),'pages': 0,'posts': []}for i in range(3): # 只爬取前三页print("page: " + str(i + 1)) # 输出页码调试用posts, next = get_posts(url, headers)pages = {'page': i + 1,'posts': posts}postss['posts'].append(pages)url = nextpostss['pages'] += 1# 每获取一页保存一次,容灾with open('tianya.json', 'w', encoding='utf-8') as f:json.dump(postss, f, ensure_ascii=False, indent=4)with open('tianya.json', 'w', encoding='utf-8') as f:json.dump(postss, f, ensure_ascii=False, indent=4) # indent=4 是为了格式化json

5. 注意事项

  1. 直接从页面提取文本标题会有一些干扰符号,需要去除

  2. 页面中部分标题有特殊样式,无法提取,需要进入该帖后利用head中的题目提取存入

6. 成品代码

import requests
from lxml import etree
import json
import re
import timedef get_posts(url, headers):raw = requests.get(url, headers=headers)code = raw.status_codeposts = []next = '' # 加载失败直接返回空,避免报错if code == 200:html = etree.HTML(raw.text)# 取下一页链接next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/@href')[0]# 第一页以外是a[3]不是a[2](两个条件不能换顺序,否则第一页会报错)if html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[2]/text()')[0] != '下一页': # 判断第二个按钮是否是下一页按钮next = "http://bbs.tianya.cn" + html.xpath('//*[@id="main"]/div[@class="short-pages-2 clearfix"]/div/a[3]/@href')[0] tbodys = html.xpath('//*[@id="main"]/div[@class="mt5"]/table/tbody')tbodys.remove(tbodys[0]) # 移除页首表头(标题   作者  点击  回复  回复时间)for tbody in tbodys:items = tbody.xpath("./tr")for item in items:title = item.xpath("./td[1]/a/text()")[0].replace('\r', '').replace('\n', '').replace('\t', '') # 帖子题目会有换行符等符号,需要去除url = "http://bbs.tianya.cn" + item.xpath("./td[1]/a/@href")[0] # 帖子链接author_id = item.xpath("./td[2]/a/text()")[0] # 作者idauthor_url = item.xpath("./td[2]/a/@href")[0] # 作者链接read_num = item.xpath("./td[3]/text()")[0] # 阅读数reply_num = item.xpath("./td[4]/text()")[0] # 回复数# 获取帖子内容postraw = requests.get(url, headers=headers) postcode = postraw.status_codeif postcode == 200:posthtml = etree.HTML(postraw.text)try:posttimeraw = posthtml.xpath('//*[@id="post_head"]/div[2]/div[2]/span[2]/text()')[0] # 发帖时间except:posttimeraw = posthtml.xpath('//*[@id="container"]/div[2]/div[3]/span[2]/text()[2]')[0] # 发帖时间post_time = re.findall(r'\d+-\d+-\d+ \d+:\d+:\d+', posttimeraw)[0]if len(title) == 0: # 处理部分因格式特殊取不到标题的帖子title = posthtml.xpath('/html/head/title/text()')[0].replace('_工薪一族_论坛_天涯社区', '')contents = posthtml.xpath('//*[@id="bd"]/div[4]/div[1]/div/div[2]/div[1]/text()') # 帖子内容(列表形式,一段一项)post_content = ''for string in contents:string = string.replace('\r', '').replace('\n', '').replace('\t', '').replace('\u3000', '') + '\n' # 去除换行符等符号,并加上段间换行符post_content += string # 将每段内容拼接起来post = {'title': title,'post_time': post_time,'author_id': author_id,'url': url,'author_url': author_url,'read_num': read_num,'reply_num': reply_num,'content': post_content}posts.append(post)print(title) # 输出帖子题目调试用return posts, nextdef main():url = 'http://bbs.tianya.cn/list.jsp?item=170'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'}postss = {'time': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),'pages': 0,'posts': []}for i in range(3): # 只爬取前三页print("page: " + str(i + 1)) # 输出页码调试用posts, next = get_posts(url, headers)pages = {'page': i + 1,'posts': posts}postss['posts'].append(pages)url = nextpostss['pages'] += 1# 每获取一页保存一次,容灾with open('tianya.json', 'w', encoding='utf-8') as f:json.dump(postss, f, ensure_ascii=False, indent=4)with open('tianya.json', 'w', encoding='utf-8') as f:json.dump(postss, f, ensure_ascii=False, indent=4) # indent=4 是为了格式化jsonif __name__ == '__main__':main()

浅谈天涯社区“工薪一族”爬虫相关推荐

  1. 浅谈网络舆情监测系统中爬虫的设计及系统架构

    前言   说到网络舆情监测想必很多人都不陌生.可以跟大家这么说吧.爬虫所能带来的商业价值适用并深存于所有小-中-大企业中,尤其做网络舆情的大数据公司所有的业务基本都必须依托于爬虫来开展它的战略布局,毕 ...

  2. 浅谈OpenStack社区COA认证考试

    本人从04年左右开始接触Linux(那个时候Google还在国内),依次做过嵌入式方面的开发.Linux系统开发及相关的认证培训工作,14年开始接触OpenStack,15年正式进入私有云的圈子里,做 ...

  3. php 爬虫去重,浅谈动态爬虫与去重(续)

    作者:Fr1day@0keeTeam 0x00 前言 在 浅谈动态爬虫与去重 中,分享了动态爬虫中触发事件.监控节点变动.URL去重等的实现方法.在接近一年的线上运行与迭代更新过程中,处理了很多bug ...

  4. 浅谈网络爬虫中广度优先算法和代码实现

    前几天给大家分享了网络爬虫中深度优先算法的介绍及其代码实现过程,没来得及上车的小伙伴们可以戳这篇文章--浅谈网络爬虫中深度优先算法和简单代码实现.今天小编给大家分享网络爬虫中广度优先算法的介绍及其代码 ...

  5. 浅谈爬虫 《一》 ===python

    浅谈爬虫 <一> ===python  ''正文之前先啰嗦一下,准确来说,在下还只是一个刚入门IT世界的菜鸟,工作近两年了,之前做前端的时候就想写博客来着,现在都转做python了,如果还 ...

  6. Python使用Scrapy爬虫框架爬取天涯社区小说“大宗师”全文

    大宗师是著名网络小说作家蛇从革的系列作品"宜昌鬼事"之一,在天涯论坛具有超级高的访问量.这个长篇小说于2015年3月17日开篇,并于2016年12月29日大结局,期间每天有7万多读 ...

  7. python 模拟浏览器selenium_浅谈python爬虫使用Selenium模拟浏览器行为

    前几天有位微信读者问我一个爬虫的问题,就是在爬去百度贴吧首页的热门动态下面的图片的时候,爬取的图片总是爬取不完整,比首页看到的少.原因他也大概分析了下,就是后面的图片是动态加载的.他的问题就是这部分动 ...

  8. 社区说|浅谈量子计算机和 Cirq

    活动时间 7 月 21 日(本周四) 20:00 - 21:00 活动日程 20:00 - 20:45 主题分享 浅谈量子计算机和 Cirq Qubit/QPU 快速入门 基础运算:QPU 加法 实例 ...

  9. 如何用python抓取文献_浅谈Python爬虫技术的网页数据抓取与分析

    浅谈 Python 爬虫技术的网页数据抓取与分析 吴永聪 [期刊名称] <计算机时代> [年 ( 卷 ), 期] 2019(000)008 [摘要] 近年来 , 随着互联网的发展 , 如何 ...

最新文章

  1. android11有哪种手机,支持安卓11系统的机型有哪些
  2. 【转】Ubuntu 14.04.3上配置并成功编译Android 6.0 r1源码
  3. Scalaz(32)- Free :lift - Monad生产线
  4. 带你了解无线网络渗透测试——无线网络嗅探工具Kismet
  5. mips j指令_MIPS处理器 MIPS指令集(上)
  6. php dump utfp,php pchart乱码-使用REST接口获取GeoServer中的...-结合 thinkPHP 分页写成自己分页类_169IT.COM...
  7. 无线数传在桥梁检测中传感器信号的采集应用
  8. centos下升级jdk版本
  9. java json u0026_特殊字符的json序列化
  10. CSharp for Jupyter Notebook
  11. php.ini 没有pdo,php.ini 没有pdo怎么办
  12. 游戏玩家场景高清桌面壁纸都是什么样的?
  13. 性能测试之Jmeter 中 CSV 如何参数化测试数据并实现自动断言
  14. 删库跑路如何预防?—— Oracle创建只读账号详细教程
  15. VS2010 asp.net web site项目使用log4net
  16. 计算器排html页面,简易计算器html页面代码
  17. Java J2SE 系列视频教程(北京上学堂马士兵老师经典java讲义)
  18. ISO27001信息安全管理体系证书,系统集成行业企业还有没办的吗?
  19. 东芝 rc100 linux,东芝RC100 M.2 NVMe固态硬盘HMB特性解读
  20. 结构体初始化的四种方法

热门文章

  1. 【亲测好用】磁盘管理器:Disk Xray Mac版
  2. 复选框的对勾颜色怎么改
  3. Ubuntu22.04安装惠普P1108打印机并在局域网内共享
  4. 我的u3d游戏编程之路
  5. #Java学习#习题一
  6. C++ #ifdef 和 #endif
  7. 瑞幸咖啡新获2.5亿美元融资背后
  8. 汇编语言笔记-keil5软件仿真及调试
  9. 【数学建模学习④】飞行管理问题
  10. 4 Kubernetes资源-Pod控制器(2)