记一次网络爬虫
 
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/