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

来源:互联网 发布:matlab 字符串数组 编辑:程序博客网 时间:2024/05/27 01:20

爬取当当书籍–进阶篇

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

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

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

1、咱们会把爬虫伪装的更像一个浏览器,并不断随机更换User-Agent,正所谓爬虫伪装术+影分身,迷惑网管(网站管理员)。2、利用Chrome自带的抓包工具捕捉AJAX异步请求的JSON数据(网页源代码中不存在或者说找不到的数据)3、最后咱们把提取到的数据存到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-8import requests,randomclass 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-8from bs4 import BeautifulSoupimport re,json,requestsclass 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-8import sys,MySQLdbreload(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

下一篇,博主将简单介绍爬虫利器—Scrapy框架,并爬取大众点评网的美食店铺,有兴趣的同学,记得关注博主呦!

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

拯救不开心!!!

阅读全文
1 0
原创粉丝点击