爬取当当书籍–进阶篇
在上一篇爬虫初体验中, 叙述了我的小爬虫的整体构架以及中心思想,并且在小伙伴的反馈下,进行了改良,加入了许多的注释,方便大家学习与交流。
在基础篇中,只是简单的爬取了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 拿去玩!
温情提示:
由于各个网站一直在维护与更新,爬取规则会有时效性,所以代码仅供参考!
拯救不开心!!!