Scrapy爬取多层网页结构数据(二)

作者: chengcxy | 来源:发表于2017-04-04 20:04 被阅读3116次

    Scrapy爬取多层网页结构数据(二)

    工作一月有余,实在是过于疲劳,没多少精力写东西,趁着清明节假期休息了2天今天来总结下Scrapy对多层网页结构数据的抓取。上一篇主要介绍的同一层网页结构的抓取,只需要调用一个函数存储数据即可,见这篇文章
    Scrapy爬取"单页面"数据(一)
    但是实际工作中需要的大都是需要进行多个页面进行解析存储数据,这个问题其实也困扰我好久,因为对框架不是很熟,一般写爬虫我都是写单独的py脚本,能在工作中应用即可,考虑到速度还是将这个框架学一学。

    梳理理解下网页结构,以省市区这样的数据结构来进行理解。进入这样一个站,有每个省,省下面有每个市,市下面有每个区,我们想得到的结构化数据肯定是这样省-市-区,如山东省-聊城市-莘县,山东省-聊城市-东昌府区。采取的策略肯定是逐层url逐层获取数据,在省这一层获取市的url,再对市的url进行解析得到每个区,进入区这一层时候需要把前两层的数据传递过来,返回给item,交给pipline进行处理。

    今天重新看了下前几个月爬的阳光电影网,发现之前写的脚本抓取的数据根本不全,没有分页,对网站抓取策略也没有吃透,经过一段时间的积累,发现只要理顺数据的逻辑,代码实现不是很难。

    (一)梳理网站 画出流程图

    红框为分类页
    第二层构造分类url
    数据存储流程
    获取到的电影下载资源

    (二)代码实现(创建项目省略,最后执行main.py即可存入mysql数据库)

    <1>items.py

    #coding:utf-8
    from scrapy import Field,Item
    class SunmoiveItem(Item):    
        cate_url = Field()
        cate_name=Field()
        cate_url_list = Field()
        moive_url=Field()
        moive_name=Field()
        moive_source=Field()
    

    <2>SunmoiveSpider.py

    #coding:utf-8
    import sys
    reload(sys)
    sys.path.append('..')
    sys.setdefaultencoding('utf-8')
    from scrapy.http import Request
    from scrapy.selector import Selector
    from scrapy.spiders import CrawlSpider
    from items import SunmoiveItem
    from bs4 import BeautifulSoup as bs
    import requests
    import re
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
        'Connection': 'keep-alive'
    }
    class SunmoiveSpider(CrawlSpider):
        name ='sunmoivespider'
        start_urls=['http://www.ygdy8.com']
        allowed_domains = ["ygdy8.com"]
        #parse 函数用于解析首页 获得每个分类的url 每个分类的名称
        def parse(self,response):
            #先定义一个空列表 存储大类的数据 然后meta参数传递给下一层
            items_1=[]
            selector=Selector(response)
            infos = selector.xpath('//div[@class="contain"]/ul/li[position()<12]')
            for info in infos:
                #在循环里对item进行实例化 类型为字典
                item = SunmoiveItem()
                cate_url = response.url + info.xpath('a/@href')[0].extract()
                cate_name = info.xpath('a/text()')[0].extract()
                # items.py中field()第一个字段
                item['cate_url']=cate_url
                # items.py中field()第二个字段
                item['cate_name'] = cate_name.encode('utf8')
                items_1.append(item)
            #此时列表items_1添加了所有获取到的分类cate_url和cate_name所有的元素是字典,每个元素是{'cate_url':'url的连接','cate_name':获取到的分类名称}
            for item in items_1:
                #对列表遍历,回调parse_item函数 请求的是每个cate_url meta将这一层的数据传递到下一层
                yield Request(url=item['cate_url'], meta={'item_1': item}, callback=self.parse_item)
    
        #parse_item 相当于进入第二层url,解析页面构造获得每个每类的分页url xpath我尝试取下方多少页找不到 用正则找到两个参数
        #'http://www.ygdy8.com/html/tv/hytv/list_7_66.html'
        #上面url中前面http://www.ygdy8.com/html/tv/hytv 为分类的url前面部分 list后第一个数字7应该是它的id 66为页码数 需要在页面中找到这两个参数
        def parse_item(self,response):
            #这里item_1接收上一层的数据
            item_1 = response.meta['item_1']
            #再次定义空列表 用来保存上一层数据和本层数据
            items=[]
            #response.url 为上一层解析得到的cate_url
            res = requests.get(response.url, headers=headers)
            res.encoding = 'gb2312'
            html = res.text.encode('utf-8')
            #解析找到两个参数 分类id 和总页数
            reg1 = r'共(.*?)页/.*?条记录'
            reg2 = r'<option value=\'(list_.*?_).*?'
            num1 = re.findall(reg1, html)
            num2 = re.findall(reg2, html)
            if len(num1) > 0:
                #response.url 为 'http://www.ygdy8.com/html/gndy/oumei/index.html'
                #每个分类分页url格式为 http://www.ygdy8.com/html/tv/hytv/list_7_66.html
                detail_url = response.url.rstrip(response.url.split('/')[-1]) + str(num2[0])
                #对总页数循环 得到每个分类分页url
                ##  http://www.ygdy8.com/html/tv/hytv/list_7_1.html、http://www.ygdy8.com/html/tv/hytv/list_7_2.html、、、、
                for page in range(1, int(num1[0]) + 1):
                    #再次将item实例化 现在item里已经有上一层的数据 现在需要把这一层的数据添加进去
                    item=SunmoiveItem()
                    cate_url_list = detail_url + str(page) + '.html'
                    if requests.get(cate_url_list, headers=headers).status_code == 200:
                        # 添加items.py中field()第三个字段
                        item['cate_url_list']=cate_url_list
                        #将上一层数据item_1字典里的传递 目前数据包含3个字段了 cate_url,cate_name,cate_url_list
                        #传递赋值接收过来的上一层数据
                        item['cate_url']=item_1['cate_url']
                        item['cate_name'] = item_1['cate_name']
                        items.append(item)
            for item in items:
                # 对列表遍历,回调parse_detail函数 进入下一层url 请求的是每个cate_url_list meta将前两层的数据传递到详情页
                yield Request(url=item['cate_url_list'], meta={'item_2':item},callback=self.parse_detail)
    
        #电影详情页的解析
        def parse_detail(self,response):
            #接收前两层数据
            item = response.meta['item_2']
            res = requests.get(response.url)
            res.encoding = 'gb2312'
            html = res.text
            soup = bs(html, 'html.parser')
            contents = soup.select('.co_content8 ul')[0].select('a')
            count = len(contents)
            print response.url, count
            for title in contents:
                print count
                moive_name = title.text.encode('utf-8')
                moive_url = "http://www.ygdy8.com/" + title['href']
                res = requests.get(moive_url)
                res.encoding = 'gb2312'
                html = res.text
                soup = bs(html, 'html.parser')
                moive_sources = soup.select('#Zoom span tbody tr td a')
                for source in moive_sources:
                    item['moive_source']=source['href']
                    item['moive_url']=moive_url
                    item['moive_name']=moive_name.encode('utf8')
                    print item['moive_name'],item['moive_url'],item['moive_source']
                    count-=1
                    yield item
    

    <3>pipelines.py (先在数据库建表)

    #coding:utf-8
    import MySQLdb
    def dbHandle():
        conn = MySQLdb.connect(
            host = "数据库ip",
            user = "数据库登陆用户",
            passwd = "密码",
            charset = "utf8",
            use_unicode = False
        )
        return conn
    
    class SunmoivePipeline(object):
        def process_item(self, item, spider):
            dbObject = dbHandle()
            cursor = dbObject.cursor()
            cursor.execute("USE local_db")
            sql = "INSERT INTO sunmoive VALUES(%s,%s,%s,%s,%s,%s)"
            try:
                cursor.execute(sql, (item['cate_name'], item['cate_url'], item['cate_url_list'],item['moive_url'],item['moive_name'], item['moive_source']))
                cursor.connection.commit()
            except BaseException as e:
                print("错误在这里>>>>", e, "<<<<<<错误在这里")
                dbObject.rollback()
            return item
    

    <4>seitings.py

    #coding:utf-8
    BOT_NAME = 'sunmoive'
    SPIDER_MODULES = ['sunmoive.spiders']
    NEWSPIDER_MODULE = 'sunmoive.spiders'
    ROBOTSTXT_OBEY = False
    USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5'
    ITEM_PIPELINES = {
       'sunmoive.pipelines.SunmoivePipeline': 300,
    }
    

    <5>main.py(pycharm里启动爬虫 和cmd命令功能一样)

    #coding:utf-8
    from scrapy import cmdline
    cmdline.execute("scrapy crawl sunmoivespider".split())
    

    (三)总结

    <1>取分页两个参数

    用xpath定义标签我是没有取到,最后还是用正则去匹配找到总页数,找第二个参数是在select>option下面找到的,对常用的数据类型列表和字典以及对字符串的处理要用的熟,学习过程还是要步步渐进,自己先造个小轮子再用框架

    <2>如何测试数据

    框架用的不熟的情况下先单独写py脚本,实现数据一致了,再往框架里套用,理顺数据传递的工作流

    相关文章

      网友评论

        本文标题:Scrapy爬取多层网页结构数据(二)

        本文链接:https://www.haomeiwen.com/subject/gyjzottx.html