在完成了基本的爬取任务之后,接到了将其封装为一个爬虫类的任务
传送门:
前言 转载注明出处。
任务介绍 1、尝试不使用 session 去进行爬取,最好能将 cookies 保存下来可以供下次使用。 2、第二个是尝试将这些封装成面向对象的方式,模拟登陆,爬取,解析,写入数据库这几个部分分离开来。
先做第二个任务
过程记录 190310 周日 创建爬虫类 1 2 3 4 5 6 7 8 class spider : ''' 爬虫类 ''' def __init__ (self ): self .session=requests.session() self .is_login=False
获取登录所需信息 获取登录信息(账号密码以及校验码)这部分与登录可以分开,单独写一个成员函数。
在输入密码这个地方,本来查到可以使用getpass
这个库里面的getpass()
函数来使用类似 linux 的密码不回显,用法如下:
1 2 3 import getpasspasswd=getpass.getpass() print (passwd)
但是直接在 pycharm 里面运行是会卡在输入那里,并且也会回显。后来查到了,这个方法是在命令行当中才管用,我试了一下在 python 命令行中使用,
1 2 3 4 5 6 >>>import getpass >>>passwd=getpass.getpass() Warning: Password input may be echoed. Password: >? 123 >>>print (passwd) 123
虽然可以使用了,但是仍然会回显。所以这个命令行说的应该不是 python 命令行,而是 cmd 或者 shell。
在虚拟环境的 cmd 里面,成功了,Password 后面未回显我的输入,下面的数字是测试用的输出,将密码打印出来。
1 2 3 (venv) F:\DEVELOP\py_develop\spider>python test.py Password: 123
不过为了方便调试代码,我还是使用了input()
函数
参考链接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def get_login_data (self,login_url ): ''' 获取登录需要的数据 :param login_url: 登录页面url :return: 一个存有登录数据的字典 ''' html = self .session.post(login_url, headers=self .headers).text soup = BeautifulSoup(html, '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' ] login_data = { 'username' : input ("请输入学号:" ), 'password' : input ("请输入密码:" ), 'btn' : '' , 'lt' : lt, 'dllt' : dllt, 'execution' : execution, '_eventId' : _eventId, 'rmShown' : rmShown } return login_data
登录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def login (self,login_url ): """ 登录并返回已经登录的会话 :return: 已经登录的会话(session) """ login_data=self .get_login_data(login_url) response = self .session.post(login_url, headers=self .headers, data=login_data) if response.url!=login_url: print ("登录成功" ) self .is_login=True else : print ("登录失败" ) return self .session
day8 进度
了解了一下 Python 类与对象的语法,尝试将代码封装到类中(一些中间代码未保留),不过想要将它改的有通用性(能够爬取其他网站)有些困难,还是先固定只能爬取信息门户
接下来的计划:将类完成之后再慢慢优化,学习使用 cookie 代替 session 保持登录,以及数据库的更多知识
190311 周一 day9 进度
图书馆借了一本 mysql 的书籍,在 mysql 命令行上练习创建数据库,表以及字段的操作
在将代码封装成类的过程中,学习了如何将参数作为一个字典传入,以及将一个字典作为参数传入
190312 周二 获取单页目录内的公告 url 目录网页的内容:
关于……的通知
关于……获奖
……
2700 条记录,分为 138 页显示,下一页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def get_url_from_cata (self,url,params ): ''' 返回当前页面的url组成的列表 :param url: 无参数的url#如:http://portal.xxx.edu.cn/detach.portal :param params:url的?后参数#如:?pageIndex=1 :return:以页面指向的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式 ''' base=url.split('/' ) base=base[0 ]+'//' +base[2 ] html = self .session.post(url,params=params).text soup = BeautifulSoup(html, 'lxml' ) rss_title = soup.find_all('a' , class_='rss-title' ) result_list=[] for url in rss_title: title=url.get_text().strip() page_url=base+'/' +url['href' ] l=(title,page_url) result_list.append(l) return result_list
获取所有目录内的公告 url 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def get_url_from_cata_all (self, url ): ''' 获取页面的底部跳转到其他页的链接并获取目录,给出一个目录页的url,获取相关的所有目录页的url并获取链接 :param url: 其中任何一个目录页的url#如:http://portal.xxx.edu.cn/detach.portal?pageIndex=1 :return:以所有页面的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式 ''' base=url.split('?' )[0 ] html = self .session.post(url).text soup = BeautifulSoup(html, 'lxml' ) reg = '共.*?条记录 分(.*?)页显示' reg = re.compile (reg, re.S) num = int (re.findall(reg, html)[0 ]) para = { 'pageIndex' : 1 , 'pageSize' : '' , '.pmn' : 'view' , '.ia' : 'false' , 'action' : 'bulletinsMoreView' , 'search' : 'true' , 'groupid' : 'all' , '.pen' : 'pe65' } ret=[] for i in range (1 ,num+1 ): ret.extend(self .get_url_from_cata(base,params=para)) para['pageIndex' ] = i return ret
day10 进度 实现了自动获取目录页数,并从每一页目录获取所有的 url,返回当前所有公告的 url 的列表
190313 周三 获取正文 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def get_page (self,url ): ''' 提取页面中的公告正文 :param url: 页面url :return: 正文 ''' html = self .session.post(url, headers=self .headers).text soup = BeautifulSoup(html, 'lxml' ) bulletin_content = soup.find('div' , class_='bulletin-content' ) bulletin_content =bulletin_content.get_text() return bulletin_content
保存到 txt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def save_by_txt (self,file_content,file_name ): ''' 获取单个公告页面的公告并保存到txt :param file_content:文件内容(str) :param file_name:输出文件名(str) :return:无 ''' reg = r'[\/:*?"<>|]' file_name = re.sub(reg, "" , file_name) with open (file_name, 'w' , encoding='utf8' ) as fout: fout.write(file_content) print ('成功保存到{}' .format (file_name))
保存到 db 1 2 3 4 5 6 7 def save_by_db (self,content,title ): db = pymysql.connect(host='127.0.0.1' , port=3306 , user='root' , passwd='root' , db='news' , charset='utf8' ) cursor = db.cursor() cursor.execute("insert into spider(`title`,`content`) values('{0}','{1}')" .format (title, content)) db.commit() print ('已经成功保存公告到数据库:“{}”' .format (title))
day11 进度 尝试将保存到数据库的函数里面的数据库参数放到函数形参处,怎么弄都觉得不太合适,于是还是将原本的代码放入
190314 周四 cookie 保持登录 参考链接:
首先是库
初始化 1 2 3 4 5 def __init__ (self,headers ): self .session=requests.session() self .is_login=False self .headers=headers self .cookiejar=http.cookiejar.LWPCookieJar('cookie.txt' )
保存 cookie 的函数 大概是将已登录的 session 对象的 cookies 转换为字典(用了一个类似列表生成式的东西,查了一下,是字典生成式,python 还真是方便,这么多简写方式),然后保存到 cookiejar 对象中,调用save()
函数来将 cookie 内容保存到第一个参数指定的文件中,即使 cookie 已经被抛弃和过期。
1 2 3 4 def save_cookie (self ): requests.utils.cookiejar_from_dict({c.name: c.value for c in self .session.cookies}, self .cookiejar) self .cookiejar.save('cookies' , ignore_discard=True , ignore_expires=True )
加载 cookie 的函数 首先初始化一个 LWPCookieJar 对象
1 load_cookiejar = http.cookiejar.LWPCookieJar()
接着从文件中加载 cookie
1 load_cookiejar.load('cookies' , ignore_discard=True , ignore_expires=True )
这里有个问题,这里如果加载失败了(没有这个文件,之前没有保存),需要知道已经失败了。所以使用一个 try 语句块测试一下。
然后把这个 LWPCookieJar 对象给转换成字典,再转换赋值给 session.cookie,这样就加载成功了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def load_cookie (self ): ''' 加载cookie :return: 是否成功 ''' load_cookiejar = http.cookiejar.LWPCookieJar() try : load_cookiejar.load('cookies' , ignore_discard=True , ignore_expires=True ) except : return False load_cookies = requests.utils.dict_from_cookiejar(load_cookiejar) self .session.cookies = requests.utils.cookiejar_from_dict(load_cookies) return True
修改后的 login() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def login (self,login_url ): """ 登录并返回已经登录的会话 :return: 已经登录的会话(session) """ if self .load_cookie(): self .is_login = True else : login_data=self .get_login_data(login_url) response = self .session.post(login_url, headers=self .headers, data=login_data) if response.url!=login_url: print ("登录成功" ) self .is_login=True self .save_cookie() else : print ("登录失败" ) return self .session
day12 进度
完成了爬虫类的封装
使用 http.cookiejar 库实现了登录一次,在 cookie 有效期内不必再次登录的功能
代码总览 import 1 2 3 4 5 import requestsfrom bs4 import BeautifulSoupimport pymysqlimport reimport http.cookiejar
构造函数 1 2 3 4 5 6 7 8 9 10 class spider : ''' 爬虫类 ''' def __init__ (self,headers ): self .session=requests.session() self .is_login=False self .headers=headers self .cookiejar=http.cookiejar.LWPCookieJar('cookie.txt' )
获取登录信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def get_login_data (self,login_url ): ''' 获取登录需要的数据 :param login_url: 登录页面url :return: 一个存有登录数据的字典 ''' html = self .session.post(login_url, headers=self .headers).text soup = BeautifulSoup(html, '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' ] login_data = { 'username' : input ("请输入学号:" ), 'password' : input ("请输入密码:" ), 'btn' : '' , 'lt' : lt, 'dllt' : dllt, 'execution' : execution, '_eventId' : _eventId, 'rmShown' : rmShown } return login_data
登录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def login (self,login_url ): """ 登录并返回已经登录的会话 :return: 已经登录的会话(session) """ if self .load_cookie(): self .is_login = True else : login_data=self .get_login_data(login_url) response = self .session.post(login_url, headers=self .headers, data=login_data) if response.url!=login_url: print ("登录成功" ) self .is_login=True self .save_cookie() else : print ("登录失败" ) return self .session
获取单页目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def get_url_from_cata (self,url,params ): ''' 返回当前页面的url组成的列表 :param url: 无参数的url :param params:url的?后参数 :return:以页面指向的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式 ''' base=url.split('/' ) base=base[0 ]+'//' +base[2 ] html = self .session.post(url,params=params).text soup = BeautifulSoup(html, 'lxml' ) rss_title = soup.find_all('a' , class_='rss-title' ) result_list=[] for url in rss_title: title=url.get_text().strip() page_url=base+'/' +url['href' ] l=(title,page_url) result_list.append(l) return result_list
获取全部目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def get_url_from_cata_all (self, url ): ''' 获取页面的底部跳转到其他页的链接并获取目录,给出一个目录页的url,获取相关的所有目录页的url并获取链接 :param url: 其中任何一个目录页的url :return:以所有页面的标题和url组成的元组为元素的列表,即[(title,content),(title,content)]的形式 ''' base=url.split('?' )[0 ] html = self .session.post(url).text soup = BeautifulSoup(html, 'lxml' ) reg = '共.*?条记录 分(.*?)页显示' num = int (re.findall(reg, html)[0 ]) para = { 'pageIndex' : 1 , 'pageSize' : '' , '.pmn' : 'view' , '.ia' : 'false' , 'action' : 'bulletinsMoreView' , 'search' : 'true' , 'groupid' : 'all' , '.pen' : 'pe65' } ret=[] for i in range (1 ,num+1 ): ret.extend(self .get_url_from_cata(base,params=para)) para['pageIndex' ] = i return ret
获取正文 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def get_page (self,url ): ''' 提取页面中的公告正文 :param url: 页面url :return: 正文 ''' html = self .session.post(url, headers=self .headers).text soup = BeautifulSoup(html, 'lxml' ) bulletin_content = soup.find('div' , class_='bulletin-content' ) bulletin_content =bulletin_content.get_text() return bulletin_content
保存到 txt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def save_by_txt (self,file_content,file_name ): ''' 获取单个公告页面的公告并保存到txt :param file_content:文件内容(str) :param file_name:输出文件名(str) :return:无 ''' reg = r'[\/:*?"<>|]' file_name = re.sub(reg, "" , file_name) with open (file_name, 'w' , encoding='utf8' ) as fout: fout.write(file_content) print ('成功保存到{}' .format (file_name))
保存到数据库 1 2 3 4 5 6 def save_by_db (self,content,title ): db = pymysql.connect(host='127.0.0.1' , port=3306 , user='root' , passwd='root' , db='news' , charset='utf8' ) cursor = db.cursor() cursor.execute("insert into spider(`title`,`content`) values('{0}','{1}')" .format (title, content)) db.commit() print ('已经成功保存公告到数据库:“{}”' .format (title))
保存 cookie 1 2 3 4 def save_cookie (self ): requests.utils.cookiejar_from_dict({c.name: c.value for c in self .session.cookies}, self .cookiejar) self .cookiejar.save('cookies' , ignore_discard=True , ignore_expires=True )
加载 cookie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def load_cookie (self ): ''' 加载cookie :return: 是否成功 ''' load_cookiejar = http.cookiejar.LWPCookieJar() try : load_cookiejar.load('cookies' , ignore_discard=True , ignore_expires=True ) except : print ('cookie加载失败' ) return False load_cookies = requests.utils.dict_from_cookiejar(load_cookiejar) self .session.cookies = requests.utils.cookiejar_from_dict(load_cookies) return True
爬取 1 2 3 4 5 6 7 8 def crawl (self,login_url,cata_url ): self .login(login_url) item_list=self .get_url_from_cata_all(cata_url) for i in item_list: title,url=i text=self .get_page(url) self .save_by_txt(text,title+'.txt' )
调用 1 2 3 4 5 6 7 8 9 headers={ 'User-Agent' :'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' , } login_url='http://xxx.xxx.xxx.cn/authserver/login?service=http%3A%2F%2Fportal.chd.edu.cn%2F' cata_url='http://xxxxxx.xxx.xxx.cn/detach.portal?pageIndex=1&pageSize=&.pmn=view&.ia=false&action=bulletinsMoreView&search=true&groupid=all&.pen=pe65' spiderman=spider(headers) spiderman.crawl(login_url, cata_url)