记一次网络爬虫
HTTP嗅探 在学校官网上http://IP:8080/opac/publicNotice/bookOverdue
,发现图书馆会定期更新借阅信息,一般一天更新一次。这些数据包括学生学号、借阅图书时间、图书名字和图书id等。可以用网络爬虫把这些数据收集下来,做数据分析。
首先打开Fiddler4
进行HTTP嗅探。发现HTTP的请求Headers如下:
1 2 3 4 5 6 7 8 9 10 11 headers = { 'Host' : 'IP' ,'User-Agent' : 'Mozilla/5.0 (X11; Ubuntu; OS_Linux x86_64; rv:48.0) Gecko/20100101 Firefox/48.0' ,'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' ,'Accept-Language' : 'en-US,en;q=0.5' ,'Accept-Encoding' : 'gzip, deflate' ,'Referer' : 'http://IP:8080/opac/publicNotice/bookOverdue' ,'Cookie' : 'JSESSIONID=***; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=zh' ,'Connection' : 'keep-alive' ,'Content-Type' : 'application/x-www-form-urlencoded' }
'Cookie'
字段保存服务器和客户端的会话信息,从内容我们也知道服务器端的部分信息(谁会在Cookie字段保存这些信息?)***
出于安全考虑隐藏了。'Content-Type'
字段表明HTTP上传的表单编码方案。其上传的表单如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {'batchno' : '' , 'classNoName' : '' , 'classno' : '' , 'endtime' : '' , 'hasNextPage' : 'true' , 'language' : '' , 'libcode' : '' , 'limitDays' : '' , 'orderBy' : '' , 'page' : '1' , 'prevPage' : '1' , 'rows' : '10' , 'searchType' : 'rdid' , 'searchValue' : '' , 'specificClassno' : '' , 'specificLibcode' : '' , 'starttime' : '' , 'state' : '' , 'type' : '' }
从表单可以看到,'page'
字段就是浏览器翻页。其他的字段也不难理解,但这里可以不用处理。有了HTTP headers和上传表单,可以通过Python构造网络请求模仿浏览器的浏览行为。'User-Agent'
字段表明所处的系统环境和浏览器类型,这个正好。
编写网络请求 编写网络请求脚本。不为学校服务器添加压力,采用单线程访问。
1 2 3 4 5 6 7 8 9 10 @time_delay(delay=seconds, with_net_delay=False ) @timethis def loop (url, page, headers, data ): global_fd.write(str (page)) data.update(page=page) r = requests.post(url, headers=headers, data=data) print (len (r.content)) html = bs4.BeautifulSoup(r.content, 'lxml' ) temp = list (html.table.find_all('tr' )) return temp
这两个装饰器分别是设置请求延时和计算请求时间。具体实现如下。
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 36 37 def timethis (func ): loop = 0 @functools.wraps(func ) def wrapper (*args, **kwargs ): nonlocal loop start = time.time() result = func(*args, **kwargs) end = time.time() loop += 1 global elapsed elapsed = end - start print ('loop: {}, network delay time {:.2f}' .format (loop, elapsed)) return result return wrapper def time_delay (delay=0 , with_net_delay=False ): def make_delay (func ): if with_net_delay: @functools.wraps(func ) def wrapper (*args, **kwargs ): number = delay-elapsed number = number if number > 0 else 0 time.sleep(number) result = func(*args, **kwargs) return result else : @functools.wraps(func ) def wrapper (*args, **kwargs ): number = delay if delay >= 0 else 0 time.sleep(delay) result = func(*args, **kwargs) return result return wrapper return make_delay
处理HTML 获取的html需要处理DOM,从中提出需要的数据。
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 refine (cell ): check = cell[0 ] loop = [] for item in cell: if item != check: loop.append(item) data = [] for item in loop: person_id = item.contents[1 ].contents[0 ] book_id = item.contents[3 ].contents[0 ] try : book_name = item.contents[5 ].contents[0 ].contents[0 ] except Exception: book_name = None book_url = item.contents[5 ].contents[0 ].attrs['href' ] borrow_time = [int (i) for i in item.contents[7 ].contents[0 ].split('-' )] return_time = [int (i) for i in item.contents[9 ].contents[0 ].split('-' )] temp = {'personid' : person_id, 'bookid' : book_id, 'bookname' : book_name, 'bookurl' : book_url, 'borrowtime' : datetime.datetime(*borrow_time), 'returntime' : datetime.datetime(*return_time)} data.append(temp) return data
但这里只有图书信息,没有学生的信息,通过一个简单的csv脚本采集学生的信息,包括班级、学号、姓名、学号,用于和图书借阅信息进行关联。
MySQL数据库表定义 获取完数据后,需要保存到数据库,方便管理和维护、查询。下面定义不同的表。
图书信息表的创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 CREATE TABLE `exbooks` ( `idx` INT (11 ) NOT NULL AUTO_INCREMENT, `personid` VARCHAR (30 ) NULL DEFAULT NULL , `bookurl` VARCHAR (50 ) NULL DEFAULT NULL , `bookid` VARCHAR (20 ) NULL DEFAULT NULL , `bookname` VARCHAR (200 ) NULL DEFAULT NULL , `borrowtime` DATETIME NULL DEFAULT NULL , `returntime` DATETIME NULL DEFAULT NULL , `hash_` VARCHAR (40 ) NULL DEFAULT NULL , PRIMARY KEY (`idx`), UNIQUE INDEX `hash_` (`hash_`), INDEX `personid_index` (`personid`) ) COLLATE = 'utf8_general_ci' ENGINE= InnoDB AUTO_INCREMENT= 81418 ;
学生信息表的创建。
1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE `students` ( `academy` VARCHAR (30 ) NULL DEFAULT NULL , `class` VARCHAR (40 ) NULL DEFAULT NULL , `personid` VARCHAR (40 ) NOT NULL , `personname` VARCHAR (10 ) NULL DEFAULT NULL , PRIMARY KEY (`personid`), UNIQUE INDEX `personid_idx` (`personid`) ) COLLATE = 'utf8_general_ci' ENGINE= InnoDB ;
我们希望图书借阅信息和学生的借阅情况对应起来,公共视图建立学生-图书的关系。
1 2 3 4 5 6 7 8 CREATE VIEW AS SELECT `e`.`bookid` AS `bookid`,`e`.`bookurl` AS `bookurl`,`e`.`bookname` AS `bookname`,`e`.`borrowtime` AS `borrowtime`, `e`.`returntime` AS `returntime`,`e`.`personid` AS `personid`, `s`.`personname` AS `personname`,`s`.`class` AS `class`, `s`.`academy` AS `academy` FROM (`exbooks` `e`LEFT JOIN `students` `s` ON ((`s`.`personid` = `e`.`personid`)))ORDER BY `e`.`personid` DESC
把数据写入MySQL 把数据写入到MySQL数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def insert_into_exbooks (item, cursor ): item = item.copy() hash_ = sha1(item) item.update({'hash_' : hash_}) item = parse_data(item) sql = create_insertion('exbooks' , item) status = cursor.execute(sql) def parse_data (data ): data['borrowtime' ] = str (data['borrowtime' ]) data['returntime' ] = str (data['returntime' ]) if data['bookname' ] is None : data['bookname' ] = 'unknow' return data
网络爬虫完毕后,可以通过SQL查询获取需要的信息。例如我想查看我最近借阅的信息。
数据库查询 1 2 3 4 5 SELECT * FROM personbooksWHERE personid = '201411611413' ORDER BY borrowtime DESC LIMIT 20
结果如下,只显示部分字段的数据。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 B0586611 /opac/book/461067 垃圾回收的算法与实现 2017-06-22 00:00:00 2017-07-22 00:00:00 B0542784 /opac/book/458656 深入理解Flask 2017-06-22 00:00:00 2017-07-22 00:00:00 B0588314 /opac/book/461787 设计模式精解及面试攻略 2017-06-22 00:00:00 2017-07-22 00:00:00 B0500025 /opac/book/399203 MySQL技术内幕 2017-06-22 00:00:00 2017-07-22 00:00:00 B0555166 /opac/book/476903 算法新解 2017-06-22 00:00:00 2017-07-22 00:00:00 B0571626 /opac/book/463880 Python参考手册 2017-06-22 00:00:00 2017-07-22 00:00:00 B0542785 /opac/book/458659 Python Web开发实战 2017-06-22 00:00:00 2017-07-22 00:00:00 B0541765 /opac/book/403913 算法之美:隐匿在数据结构背后的原理 2017-06-22 00:00:00 2017-07-22 00:00:00 B0597453 /opac/book/406761 数据结构与算法:Python语言描述 2017-04-01 00:00:00 2017-05-01 00:00:00 B0450349 /opac/book/367335 NoSQL数据库技术实战 2017-03-29 00:00:00 2017-04-28 00:00:00 B0493726 /opac/book/390730 RabbitMQ实战:高效部署分布式消息队列:distrbuted messaging for everyone 2017-03-29 00:00:00 2017-04-28 00:00:00 B0586611 /opac/book/461067 垃圾回收的算法与实现 2017-03-29 00:00:00 2017-04-28 00:00:00 B0571626 /opac/book/463880 Python参考手册 2017-03-17 00:00:00 2017-04-24 00:00:00 B0569142 /opac/book/459875 Python面向对象编程指南 2017-03-08 00:00:00 2017-04-24 00:00:00 B0542785 /opac/book/458659 Python Web开发实战 2017-02-26 00:00:00 2017-04-24 00:00:00 B0500025 /opac/book/399203 MySQL技术内幕 2017-02-26 00:00:00 2017-04-24 00:00:00 B0346781 /opac/book/340866 HTTP权威指南 2017-02-26 00:00:00 2017-04-24 00:00:00 B0517201 /opac/book/402756 超实用的JavaScript代码段 2017-02-26 00:00:00 2017-04-24 00:00:00 B0516474 /opac/book/402699 超实用的jQuery代码段 2017-02-26 00:00:00 2017-04-24 00:00:00 B0482313 /opac/book/381087 Python黑帽子:黑客与渗透测试编程之道:python programming for hackers and pentesters 2017-02-26 00:00:00 2017-04-24 00:00:00
现在这些数据一共有四万多条,可以做很多有意义的数据分析。例如挖掘阅读学习兴趣相似的人等。以后有空分享。
转载请包括本文地址:https://allenwind.github.io/blog/2350 更多文章请参考:https://allenwind.github.io/blog/archives/