利用Python爬取学校信息门户新闻并存入数据库

任务:利用Python爬虫的相关知识爬取学校信息门户并将数据存入数据库中
用到并且需要掌握的Python第三方库

import requests from bs4 import BeautifulSoup import re import pymysql import bs4 

需要解决的关键问题
1.利用requests进行模拟登录,并用session保持会话。
2.利用bs4库对登录后的页面进行解析,获取网页新闻的链接。
3.利用bs4库和re库如何对新闻页面进行解析,获取新闻的内容。
4.利用bs4库爬取新闻内容,并利用pymysql库保存到数据库中。

1.模拟登陆

我一开始时利用requests中的post方法向登录界面提交数据,自信满满,认为自己能够登录成功,就写出了以下幼稚的代码。

postUrl = url postHeader = { 'User-Agent': User-Agent } data = {'username': account, 'password': password} try: r = requests.post(url=postUrl, data=data, headers=postHeader) html = r.text soup = BeautifulSoup(html, 'lxml') print(soup.prettify()) except: print('爬取失败') 

结果没有显示爬取失败,我以为自己成功了 ,可等我把输出的内容一看,这不就是登录界面的源码?返回了登录界面?完全没有任何网页新闻的链接,我才意识到我失败了。让我郁闷了好久,
有一天我在CSDN上看到登录时可以在headers中加入cookies,于是我又去试了下学校的信息门户。

postUrl = url postHeader = { 'User-Agent': User-Agent 'Cookie': 'route=c4b118a65a48d46153e675807ba40b24; JSESSIONID_ids2=0001o4YS2gmK_LGPzJczYckMjGH:-LH1ERP' } data = {'username': account, 'password': password} try: r = requests.post(url=postUrl, data=data, headers=postHeader) html = r.text soup = BeautifulSoup(html, 'lxml') print(soup.prettify()) except: print('爬取失败') 

结果可想而知,还是登录界面的源码。又自闭了好几天,有一天看到一篇写session的文章,session是用来保持会话的啊,我这还没登上去的呢,算了,死马当作活马医,用session来post数据,看看能不能登上去,我又双叒叕把代码改了。

postUrl = url postHeader = { 'User-Agent': User-Agent } session = requests.session() postData = {'username': account, 'password': password} r = session.post(postUrl, headers=postHeader, data=postData) html = r.text soup = BeautifulSoup(html, 'lxml') print(soup.prettify()) 

我又双叒叕把代码改了之后又双叒叕失败了。我自闭了,某天晚上看了学长的博客后恍然大悟,我没有注意到每次的登录校验码都不一样,每一次登陆时的时候登录效验码都不一样,应当先用session登录失败一次,获取此时的登录效验码,然后再用登录效验码和正确用户名密码登录信息门户,这下登录的问题终于解决了。

# 获取登录时的效验码 soup = BeautifulSoup(session.post(postUrl, headers=postHeader).text, 'lxml') lt = soup.find('input', {'name': 'lt'})['value'] dllt = soup.find('input', {'name': 'dllt'})['value'] execution = soup.find('input', {'name': 'execution'})['value'] _eventId = soup.find('input', {'name': '_eventId'})['value'] rmShown = soup.find('input', {'name': 'rmShown'})['value'] postData = { 'username': account, 'password': password, 'btn': '', 'lt': lt, 'dllt': dllt, 'execution': execution, '_eventId': _eventId, 'rmShown': rmShown } 

最后将登录封装成一个函数login,登录问题终于解决了,终于又一点点小小的进度了。

def login(account, password): postUrl = url postHeader = { 'User-Agent': User-Agent } session = requests.session() soup = BeautifulSoup(session.post(postUrl, headers=postHeader).text, 'lxml') lt = soup.find('input', {'name': 'lt'})['value'] dllt = soup.find('input', {'name': 'dllt'})['value'] execution = soup.find('input', {'name': 'execution'})['value'] _eventId = soup.find('input', {'name': '_eventId'})['value'] rmShown = soup.find('input', {'name': 'rmShown'})['value'] postData = { 'username': account, 'password': password, 'btn': '', 'lt': lt, 'dllt': dllt, 'execution': execution, '_eventId': _eventId, 'rmShown': rmShown } r = session.post(postUrl, headers=postHeader, data=postData) return session 

2.获取网页新闻标题和链接

在新闻目录页面进行翻页时,发现只有pageIndex参数会变,当从第一页翻到第二页时,pageIndex会从1变到2,所以对于新闻目录页面建立一个字典,每次翻页时只改变里面的pageIndex参数即可。

para = { 'pageIndex': 1, 'pageSize': '', '.pmn': 'view', '.ia': 'false', 'action': 'bulletinsMoreView', 'search': 'true', 'groupid': 'all', '.pen': 'pe65' } 

检查新闻目录页面发现,每一条新闻的标题会保存在a标签下子标签span标签的字符串中,并且左右带有空格,可以用字符串的strip()函数自动去除空格,新闻链接保存在a标签下href属性当中,建两个列表分别存放新闻标题和新闻链接。

for pageIndex in range(1, pageNum + 1): para['pageIndex'] = pageIndex soup = BeautifulSoup(session.post(originUrl, params=para).text, 'lxml') for link in soup.find_all('a'): announcementTitle = str(link.span.string).strip() announcementLink = link['href'] 

但是运行后报错了,于是回去把检查新闻目录的页面重新看了一遍,发现有的a标签是没有span子标签。所以会报错,还要加上个class_='rss-title'属性,可是当我尝试打开爬取的新闻链接时又双叒叕出错了,当我把获取的链接打印出来,我发现我太太幼稚了,没有在前面加学校网站的域名。
announcementLink = 'http://' + link['href']
最后终于成功的获取了每一页新闻目录中新闻的标题和新闻的链接,保存在一个announcementList的列表里,列表里的每个元素都是一个包含新闻标题和新闻链接的列表。

announcementList = [] for pageIndex in range(1, pageNum + 1): para['pageIndex'] = pageIndex soup = BeautifulSoup(session.post(originUrl, params=para).text, 'lxml') for link in soup.find_all('a', class_='rss-title'): announcementTemp = [] announcementTitle = str(link.span.string).strip() announcementLink = 'http://' + link['href'] announcementTemp.append(announcementTitle) announcementTemp.append(announcementLink) announcementList.append(announcementTemp) 

最后和login函数,并用session来保持会话,将获取新闻标题和新闻链接封装成一个函数getLink。

def getLink(pageNum, account, password): originUrl = url para = { 'pageIndex': 1, 'pageSize': '', '.pmn': 'view', '.ia': 'false', 'action': 'bulletinsMoreView', 'search': 'true', 'groupid': 'all', '.pen': 'pe65' } session = login(account, password) announcementList = [] for pageIndex in range(1, pageNum + 1): para['pageIndex'] = pageIndex soup = BeautifulSoup(session.post(originUrl, params=para).text, 'lxml') for link in soup.find_all('a', class_='rss-title'): announcementTemp = [] announcementTitle = str(link.span.string).strip() announcementLink = 'http://' + link['href'] announcementTemp.append(announcementTitle) announcementTemp.append(announcementLink) announcementList.append(announcementTemp) 

3获取新闻的内容

检查单个新闻的页面,发现每个新闻的内容都存在每个div下的p标签下的span标签里面的字符串中,吸取了上次获取每个新闻标题和链接的教训,应当加入限制条件class_='bulletin-content',避免了在爬取新闻内容时报错。所以我们就可以用bulletinContent来保存带有class_='bulletin-content'属性的div标签下的所有内容,然后再利用BeautifulSoup库里面的find_all函数找到所有的p标签,再跟获取新闻标题一样,利用strip()函数去除空格,当然,有一些span标签下没有字符串,那当然不用处理。

text = '' bulletinContent = soup.find('div', class_='bulletin-content') for p in bulletinContent.find_all('p'): if p.span != None: text += (str(p.get_text()).strip() + '\n') 

但运行时又又又报错了!!!于是我把第一页的所有新闻都检查了一遍,发现有一两个新闻中只有附件,没有内容,这就导致了该新闻页中是没有p标签的,所以就会找不到p标签,当然会报错了!!!找到了问题的根本就很容易解决了,用isinstance()函数判断bulletinContent中是否存在标签,如果有,就寻找p标签并获取新闻内容,没有就说明该新闻只有附件,直接跳过该新闻的处理。

text = '' bulletinContent = soup.find('div', class_='bulletin-content') if isinstance(bulletinContent, bs4.element.Tag): for p in bulletinContent.find_all('p'): if p.span != None: text += (str(p.get_text()).strip() + '\n') 

同样,在进入单个新闻页的时候还是用session来保持回话,来实现页面的跳转,最后封装成一个函数getTitleAndText。

def getTitleAndText(url, session, titile, index): postHeader = { 'User-Agent': User-Agent } text = '' html = session.post(url, headers=postHeader).text; soup = BeautifulSoup(html, 'lxml') bulletinContent = soup.find('div', class_='bulletin-content') if isinstance(bulletinContent, bs4.element.Tag): for p in bulletinContent.find_all('p'): if p.span != None: text += (str(p.get_text()).strip() + '\n') title = str(index) + '.' + title 

就这样,成功的把新闻标题和新闻的内容打印出来了,下一步就是要把新闻标题和内容保存到数据库了,马上就要成功了!!!

将新闻标题和内容保存到MySql数据库

在CSDN上学习了几天后,利用mysql中的连接MySql Workbench。

connection = pymysql.connect(host='127.0.0.1', port=3306, database='python', user='root', password='password', charset='utf-8') cursor = connection.cursor() 

database就是你要保存数据的数据库,要提前在MySql Workbench中建立,user统一写的都是root,password就是你在安装MySql Workbench时设的密码,charset是编码格式,但我一运行就会报错,写个爬虫真是坎坷,又去CSDN上搜了搜,发现原来是自己的编码格式写错了,应该写utf8,这个点好坑,改代码吧。

connection = pymysql.connect(host='127.0.0.1', port=3306, database='python', user='root', password='password', charset='utf8') cursor = connection.cursor() 

在CSDN上学习的过程中,自己发现了两种把数据保存到数据库的方法,两种方法都是用到了execute()函数,但写的形式有所不一样,下面分别介绍。
第一种:

cursor.execute("insert into news(`title`, `content`) values('{0}', '{1}')".format(title, text)) connection.commit() 

news是在database数据中建立的一个表,需要提前建立,其中这个表有名称为title,content的两栏,cursor.execute()表示使用cursor游标来执行MySql的相关语句,最后connection.commit()表示向MySql中提交数据。
第二种:

query = 'insert into news(title, content) values(%s, %s)' values = (title, text) cursor.execute(query, values) connection.commit() 

这种方法和第一种是类似的,只是MySql和提交的数据分开了,再通过cursor.execute()来执行。
但等我一运行上面的任何一种,都会报错,大概是说类型不对什么的,我翻看了一下数据库,自己已经建立了一个名为news的表,表的两栏为title和content,没错啊,为什么一运行会报错呢?我又试了试把上面的title和text都换成了数字,结果成功了!为什么呢?news表中也出现了我刚换的数字。原来我建立表的时候没有把每一栏的数据类型改过来,写的都是int类型,肯定会报错啊。知道原因后,我立马就去CSDN上搜了一下MySql数据类型,了解到VARCHAR(M)是用来存字符串的,M越大(0 <= M && M <= 65535),能够寸的长度越长,我将M设为65530但是建立news表时报错了,只能调到240了。一运行,上次的错误是没了,但是又出现了一个新的错误,大概是说文本太长,看来新闻标题是完全在M = 240时存下的,但是内容完全存不下,在DSDN上看看还有没有其他数据类型,看到一个LONGTEXT,试了试,真的成功了!!!最后封装为firstWaySaveMysql()和secondWaySaveMysql()函数。

def firstWaySaveMysql(title, text): connection = pymysql.connect(host='127.0.0.1', port=3306, database='python', user='root', password='password', charset='utf8') cursor = connection.cursor() cursor.execute("insert into news(`title`, `content`) values('{0}', '{1}')".format(title, text)) connection.commit() 
def secondWaySaveMysql(title, text): connection = pymysql.connect(host='127.0.0.1', port=3306, database='python', user='root', password='password', charset='utf8') cursor = connection.cursor() query = 'insert into news(title, content) values(%s, %s)' values = (title, text) cursor.execute(query, values) connection.commit() 

对比了两种方法的速度,发现第一种方法还是要比第二种方法快一点。

这次爬虫让我学到的不仅仅是一些第三方库的使用,还有面对一次又一次的报错,自己应当去百度,CSDN,询问学长,或者自己捣鼓捣鼓,应该通过这些途径去解决一次次的报错,不能放弃,当你亲手编写的程序真的把学校信息门户新闻爬取的那一刻,你就会知道我为什么没有放弃,你就会体会到那种开心的感觉,我的ACM队友说:“为什么要打ACM,因为当你的代码被Accept的时候,你就会很开心,开心到一天都没有烦恼。”,我想任何事都是如此,当你坚持下来后,成功后,你才能体会到这种感觉,加油???

原文链接:https://blog.csdn.net/qq_44301813/article/details/88783474?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165277607816782184625433%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165277607816782184625433&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~times_rank-7-88783474-null-null.nonecase&utm_term=%E6%96%B0%E9%97%BB

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
文明发言,共建和谐米科社区
提交
头像

昵称

取消
昵称表情图片