php网页抓取标题(爬取济南市中“滚动预警”菜单中的文章标题、内容与发布时间)
优采云 发布时间: 2022-01-19 12:01php网页抓取标题(爬取济南市中“滚动预警”菜单中的文章标题、内容与发布时间)
爬虫用的比较少,每次用都会手生,特此记录下实战经验。
项目要求
需要爬取济南市政网“滚动预警”菜单中的文章,包括文章标题、文章正文、文章时间,并保存为一个txt文件。
项目分析1、确定可以爬取什么
首先查看网站的robots.txt文件,发现该文件不存在。因此,可以正常抓取相关公开信息。
2、确定页面的加载方式
网页加载可以分为静态加载和动态加载。
网页右键->选择查看源代码,即网页的静态代码。在网页上右击-> Inspect 查看浏览器当前呈现的内容。
如果两者一致,则静态加载网页。此时,通常可以使用requests.get获取网页数据。
如果两者不一致,则动态加载网页。这时候需要通过开发者后台查看本地发送到服务器的交互数据(XHR)。
每3页,网页会冻结一小段时间,然后加载。同时可以找到一个额外的XHR数据,如图。此时请求的URL如上图所示,并且在URL中标注了开始数据和结束数据。同时网页的请求方式为POST。
3、查看提交的表单内容
如图所示,提交的表单主要收录七条数据。看看网站的其他页面,大致可以猜到:
webid用来区分不同的大板块,columnid用来区分各个大板块中的小板块,其他属性未知。翻页过程中,只有url发生变化,提交的表单内容是固定的。
还可以发现,紧急新闻和sliding alerts请求的url是一样的,不同的是form数据:紧急新闻的columnid是29112,sliding alert的columnid是34053。
4、获取文章标题、内容和发表时间
通过上面的分析,已经可以通过post的方式获取到各个页面目录的源码了。再次,基于此,需要通过目录的链接进入每个文章的页面,提取标题、文字和时间。
通过bs4函数工具和正则表达式,可以将链接内容提取出来存储在Linklist中。
点击链接跳转,可以发现内容页面是静态加载的。这时候可以通过get或者post方法获取文章的内容。我这里还是用之前封装好的post方法。
分别提取文章标题、内容和时间,并将它们存储在title_list、content_list和time_list中。
5、寻找自动翻页的模式
通过以上操作,可以得到一次加载的内容,即三页内容(27条新闻),下面会通过寻找模式多次加载。
寻找模式:
第 1-3 页:
第 4-6 页:
255 页(最后一页):
发现只更改了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'(.*?)?', 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、建立新连接失败:[WinError 10060]连接尝试失败,因为连接方一段时间后没有正确回复或连接的主机没有响应。'))
问题分析:错误可能是ip被封或者爬虫访问速度太快,服务器来不及响应。
解决方法:每次gethtml都加time.sleep(1),这样每次爬取的间隔为1秒。如果还是报错,尝试使用代理ip。