爬虫再体验--爬取当当书籍--进阶篇

爬取当当书籍–进阶篇

在上一篇爬虫初体验中, 叙述了我的小爬虫的整体构架以及中心思想,并且在小伙伴的反馈下,进行了改良,加入了许多的注释,方便大家学习与交流。

在基础篇中,只是简单的爬取了3个字段,小伪装了一下浏览器,并写入到了文件当中,是不是感觉有些low档次呢?

那么今天,咱们就来搞一些高大上的东西,


  • 咱们会把爬虫伪装的更像一个浏览器,并不断随机更换User-Agent,正所谓爬虫伪装术+影分身,迷惑网管(网站管理员)。

  • 利用Chrome自带的抓包工具捕捉AJAX异步请求的JSON数据(网页源代码中不存在或者说找不到的数据)

  • 最后咱们把提取到的数据存到MySql数据库里


温馨提示
   本篇重点讲解以上三点,若有对细节和别的地方产生困惑的话,请阅读博主上一篇爬虫初体验--爬取当当书籍

爬虫的基本结构与上一篇文章一样,咱们再来回顾一下

分为5个文件:
分别是1、spider_main.py     即主函数,用于启动爬虫
     2、url_manager.py     这个是url管理器,用于管理url
     3、html_downloader.py 这个是html下载器,用于下载给定url 的源码
     4、html_parser.py     这个是html解析器,用于提取你想要的信息
     5、html_outputer.py   这个是html输出器,用于把提取的信息存储到文件或者数据库

咱们今天的重点,是分别在html下载器、html解析器、html输出器上做文章。

主函数以及url管理器基本没变化,源码我会在文章末尾打包给各位

好了,开始进入正题

一、爬虫伪装术+影分身

有时候,我们的小爬虫爬着爬着就被网管拒之门外(403状态码拒绝访问),甚至刚开始就已经结束了。
那可怎么办呢,别着急,咱们来学两招

咱们在html下载器(html_downloader.py)里做文章
上一次咱们简单的加入了User-Agent,这次呢,咱们多加入几个字段

headers = {
      'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) 		  AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
	                                  'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
	                                  
          'Accept-Encoding':'gzip, deflate, sdch, br',
          'Accept-Language':'zh-CN,zh;q=0.8',
          'Connection':'keep-alive',
          'Host':'product.dangdang.com',
		  'Cookie':'',
            }

话说是这样,但是这些字段去哪里找呢?
来,咱们一起来找找,
首先打开浏览器,我用的Chrome,找到一个书籍网页,打开后按F12,出现调试窗口,点击菜单栏中的Network,然后点击下方的All
这里写图片描述

却发现什么也没有,不要紧,点击F5刷新网页,之后出来一大堆东西,随便点击一个(尽量选择含有较多Headers的),右方的Headers栏中会出来一大堆东西,Ruquests Headers中的东西,就是我们想要的
这里写图片描述
这些都是浏览器的标识,就相当于浏览器的身份证一样
想要了解每个字段的含义的话,推荐去看Requests Headers

到这里,咱们的伪装术完成了。

------------------------------------------这是一个搞事的分割线-----------------------------------------

单伪装成一个浏览器,可能对某些网站还是不太好使,那咱们就来个影分身

咱们随机换取User-Agent字段,让网管误以为每次访问都是不同的浏览器
首先用一个list列表来存放多个User-Agent,然后再利用random模块的 choice方法,随机抽取一个User-Agent,放进headers里去,代码如下:

# coding: utf-8
import requests,random
class HtmlDownloader(object):
	"""docstring for HtmlDownloader"""
	def __init__(self):
	#用来存放User-Agent的list
		self.user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
           
        ]
	def download(self,url):
		UA = random.choice(self.user_agent_list)
		if url == None:
			return 
		headers = {
      'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) 		  AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
	                                  'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
	                                  
          'Accept-Encoding':'gzip, deflate, sdch, br',
          'Accept-Language':'zh-CN,zh;q=0.8',
          'Connection':'keep-alive',
          'Host':'product.dangdang.com',
		  'Cookie':'',
            }
		# print "headers:%s"%(headers)
		res = requests.get(url,headers=headers,timeout=20)
		html = res.text
		return html

以上只是放入了几个User-Agent而已,我个人又整理了300多个,需要的话,拿去玩!
300多个User-Agent

好了,影分身+伪装术到此结束,是不是有点意思呢!

二、捕捉AJAX异步请求的JSON数据

有时候我们虽然拿到了整个页面的html源代码,但是怎么也找不到咱们想要拿的数据,是不是好奇怪。
这是因为那些数据是用AJAX加载出来的,如果不懂什么是AJAX,推荐看百度百科AJAX

这次咱们依然用到调试者模式,不过这次咱们的目的更加明确,就是拿通过AJAX异步请求才拿到的数据(源代码里找不到的),这次咱们拿的是总评论数,好评论数,中评,差评以及好评率(之前的做法是拿不到的,不信你试试)

还是Network,点击下方的 XHR,点击F5,然后一个一个找,右边的Preview会显示data内容,最终咱们找到了咱们想要的那个AJAX请求
这里写图片描述

之后咱们找到他所对应的url,还是在Headers里边
这里写图片描述

这个就是获取这个数据的url,你可以复制到地址栏里访问一下,返回的数据是json格式,不懂json的同学,可以百度一下。
url拿到了,咱们也就离成功不远了,
为了方便使用,我在url_parser.py里边又定义了一个方法,用来进行AJAX请求并放回的下载json数据。
http://product.dangdang.com/index.php?r=comment%2Flist&productId=25120084&categoryPath=01.01.02.00.00.00&mainProductId=25120084&mediumId=0&pageIndex=1&sortType=1&filterType=1&isSystem=1&tagId=0&tagFilterCount=0
只拿回来这个url还不够,咱们还需要对它进行分析,因为有些参数是随着不同的书籍是变的,我把多个同类的url做对比,发现其中的“productId”、“categoryPath”、“mainProductId”是变化的。
于是我们在书籍的源码中拿到这三个字段然后进行拼接。形成每本书籍特有的AJAX请求
代码如下:

# coding: utf-8
from bs4 import BeautifulSoup
import re,json,requests
class HtmlParser(object):
	"""docstring for HtmlParser"""

	def get_new_urls(self,page_url,soup):
		new_urls = set()
		links = soup.find_all('a',href=re.compile(r"http://category.dangdang.com/[.\w]+html|http://product.dangdang.com/[.\w]+html"))
		for link in links:
			new_url = link['href']
			new_urls.add(new_url)
		return new_urls
	
	def get_new_data(self,page_url,html,soup):
		
		data = {}
		link_node = soup.find('div',class_='name_info')
		if link_node != None:
			data['url'] = page_url

			h1_node = link_node.find('h1')	
			data['title'] = h1_node.get_text().strip()				#书名

			price_node = soup.find('p',id='dd-price')	
			data['price'] = price_node.get_text().strip()[1:]		#价钱

			#用正则表达式拿取
			ma = re.search(r'"productId":"[\d]+"',html)
			productId =  eval(ma.group().split(':')[-1])
			ma = re.search(r'"categoryPath":"[\d.]+"',html)
			categoryPath =  eval(ma.group().split(':')[-1])
			# print page_url
			ma = re.search(r'"mainProductId":"[\d.]+"',html)
			mainProductId =  eval(ma.group().split(':')[-1])
		   
			#对Ajax的url进行拼接
			json_url = 'http://product.dangdang.com/index.php?r=comment%2Flist&productId={productId}&categoryPath={categoryPath}&mainProductId={mainProductId}&mediumId=0&pageIndex=1&sortType=1&filterType=1&isSystem=1&tagId=0&tagFilterCount=0'.format(productId=productId,categoryPath=categoryPath,mainProductId=mainProductId)
			#调用方法,下载下来json数据
			json_html = json.loads(self.getJsonText(json_url))
			summary = json_html['data']['summary']	
			data['all_comment_num'] = summary['total_comment_num']				#总评论数
			data['good_comment_num'] = summary['total_crazy_count']				#好评数
			data['middle_comment_num'] = summary['total_indifferent_count']		#中评数
			data['bad_comment_num'] = summary['total_detest_count']				#差评数
			data['good_rate'] = summary['goodRate']								#好评率
		return data
		
	#用于加载请求AJAX并获取json数据的方法
	def getJsonText(self,url):
		try:
			r = requests.get(url, timeout = 30)
			r.raise_for_status()
			r.encoding = r.apparent_encoding
			return r.text
		except:
			print '获取失败'
			return ''

	def parse(self,page_url,html):
		if page_url == None:
			return
		soup = BeautifulSoup(html,'html.parser')
		new_urls = self.get_new_urls(page_url,soup)
		new_data = self.get_new_data(page_url,html,soup)
		return new_urls,new_data

以上用到了正则表达式(Python正则教程
还有Python内置json库(Python的json教程
这一部分就算完成了,有什么不懂的,欢迎骚扰博主

三、存储到数据库

Python连接到数据库比java等其他语言简单多了,只需要简单的几句话
推荐教程Python对数据库操作教程
(偷个懒,写了这么多,太累了,大家去看我给你们推荐的教程吧,嘿嘿)
下面附上代码,其中有文件写入部分,和提交到数据库部分:

# coding: utf-8
import sys,MySQLdb
reload(sys)
sys.setdefaultencoding('utf-8')
class HtmlOutputer(object):
	"""docstring for Outputer"""
	def __init__(self):
		self.datas = []

	def collect(self,data):
		if data == None or len(data) == 0:
			return 
		self.datas.append(data)
	#这个依然是输出到html文件里
	def output_html(self):
		f = open('out.html','w')
		try:
			f.write('<html>')
			f.write('<body>')
			f.write('<table>')
			for data in self.datas:
				f.write('<tr>')
				f.write('<td>%s</td><td>%s</td><td>%s</td>'%(data['url'],data['title'].encode('utf-8'),data['price'].encode('utf-8')))
				f.write('<td>%s</td>'%(data['all_comment_num']))
				f.write('<td>%s</td>'%(data['good_comment_num']))
				f.write('<td>%s</td>'%(data['middle_comment_num']))
				f.write('<td>%s</td>'%(data['bad_comment_num']))
				f.write('<td>%s</td>'%(data['good_rate']))
				f.write('</tr>')
			f.write('</table>')
			f.write('</body>')
			f.write('</html>')
		except:
			print '输出失败!'
		finally:
			f.close()
	#这个才是今天的重点,存储到数据库里
	def output_mysql(self):
		# print 'mysql'
		conn = MySQLdb.Connect(
								host = '127.0.0.1',
								port = 3306,
								user = 'root',
								passwd = '123',
								db = 'python_1',
								charset = 'utf8'

								)
		cursor = conn.cursor()
		print 'datas:%d'%(len(self.datas))
		for data in self.datas:
			try:
			# print data
			# print len(data)
				sql = "INSERT INTO book(url,title,price,all_comment_num,good_comment_num,middle_comment_num,bad_comment_num,good_rate) VALUES('%s','%s','%s','%s','%s','%s','%s','%s')"%(data['url'],data['title'].encode('utf-8'),data['price'].encode('utf-8'),
					data['all_comment_num'],data['good_comment_num'],data['middle_comment_num'],data['bad_comment_num'],data['good_rate']
					)
				cursor.execute(sql)
				# print 'inserting'
				conn.commit()
				print 'insert success'
			except:
				print '插入失败!'
				continue
		print 'insert end!'
		cursor.close()
		conn.close()

		

代码调试ok,有图有真相

这里写图片描述

以上就是所有的内容,篇幅有限,有些地方说的不够详细还请各位谅解,有什么不明白的,或者什么我写的不好的,需要改良的话,可以和博主交流交流嘛,邮箱:1131726190@qq.com

源码奉上:http://pan.baidu.com/s/1qYymLlU 拿去玩!
温情提示:
由于各个网站一直在维护与更新,爬取规则会有时效性,所以代码仅供参考!

拯救不开心!!!

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值