zoukankan      html  css  js  c++  java
  • Python爬虫——使用 lxml 解析器爬取汽车之家二手车信息

    本次爬虫的目标是汽车之家的二手车销售信息,范围是全国,不过很可惜,汽车之家只显示100页信息,每页48条,也就是说最多只能够爬取4800条信息。

    由于这次爬虫的主要目的是使用lxml解析器,所以在信息的查找上面完全只会涉及lxml中选择器的用法,虽然lxml可以同时使用CSS选择器和Xpath选择器,但是为了更加突出效果,暂且只使用Xpath。

    爬虫老套路,分为3个步骤:

    1. 分析网页信息构成,找到切入点
    2. 获取网页,提取有效信息
    3. 储存信息

    网页分析

    网页结构分析的一般思路是先找到第一个需要爬取的链接,然后看看后面的链接是以什么方式构成的,进而选择一种方式全量爬取(一般使用循环或者递归的方式)。

    找到切入点——第一个 URL

    通过查看链接构成的规律很容易可以发现,汽车之家二手车的信息是由很多选择项来构成URL的,比如按照品牌,或者车系价格城市等等,如果选择清空筛选,那就得到了全国二手车信息的URL,这正是我需要的,URL为http://www.che168.com/china/list/

    分析【下一页】链接的构成

    第一页的链接很容易得到,然后可以看到,二手车的信息总计只有100页,每页48个信息,也就是总共有4800个信息可以爬取到。

    通过点击“下一页”可以直接看到第2页的链接大概是http://www.che168.com/china/a0_0msdgscncgpi1ltocsp2exb1x0/,继续点击下一页,查看第3页、第4页的链接http://www.che168.com/china/a0_0msdgscncgpi1ltocsp3exb1x0/可以看到,规律很明显,每页的链接构成除了页码中的数字不同外,其他部分完全一样。

    选择一种构造链接的方法

    通过上面的分析,构造所有100页的链接是件很简单的事情,只需要把链接中的数字部分循环替换一下就行了,这就是循环的方式了。这个方式对付这种链接很有规律的URL在适合不过了,具体参考代码:

    for i in range(1,101):
        url = 'http://www.che168.com/china/a0_0msdgscncgpi1ltocsp{}exb1x0/'.format(i)
    

    不过,为了更好的适应更加多变的URL,我更加倾向于使用递归的方式来爬取下一页的信息。因此本篇爬虫中不适用上面这种循环爬取的方式,转而使用递归爬取。

    所谓递归,首先找到一个递归的出口,也就是爬虫的终点。对于这个爬虫,终点就是当爬到第100页的时候就要结束,既然思路明确了,那可以看看第100页与其他页面有什么不同。

    提取信息

    递归提取下一页链接

    通过分析,可以看到1-99页都有一个“下一页”的按钮,而最后一页是没有这个按钮的,这就是出口。只需要设置一个判断就行了:

    def get_items(self,url):
        html = requests.get(url,headers=self.headers).text
        selector = etree.HTML(html)
        next_page = selector.xpath('//*[@id="listpagination"]/a[last()]/@href')[0]
        next_text = selector.xpath('//*[@id="listpagination"]/a[last()]/text()')[0]
        url_list = selector.xpath('//*[@id="viewlist_ul"]/li/a/@href')
        for each in url_list:
            the_url = 'http://www.che168.com'+each
            self.get_infos(the_url)
        if next_text == '下一页':
            next_url = 'http://www.che168.com/china'+next_page
            self.get_items(next_url)
    

    上面这段代码主要包含下面几个步骤:

    html = requests.get(url,headers=self.headers).text
    

    这一句是通过requests来获取网页结构,形成标签树。

    selector = etree.HTML(html)
    next_page = selector.xpath('//*[@id="listpagination"]/a[last()]/@href')[0]
    next_text = selector.xpath('//*[@id="listpagination"]/a[last()]/text()')[0]
    

    上面3句的用意是使用lxml解析网页,然后使用xpath选择器找到下一页的链接,同时尝试找到“下一页”这3个字。

    每当找到“下一页”这个按钮,就执行if下面的代码,也就是把找到的下一页链接放入函数中去继续执行,这就形成了递归。当然,前面也说过了,只有1-99页是有这个按钮的,所以到了第100页就找不到这3个字了,这里的if判断就会停止执行, 递归也就结束了。

    这一段就是递归的判断:

    if next_text == '下一页':
        next_url = 'http://www.che168.com/china'+next_page
        self.get_items(next_url)
    

    提取二手车主页链接

    通过查看页面就能看出来,每个页面有48个二手车信息,但是由于页面信息太少了,所以最好再把每个二手车的主页面打开,所以需要先提取到每个二手车的主页面的链接

    二手车信息

    这段代码就是提取每个页面的所有二手车链接,并且对每个链接执行函数去提取有效信息:

    url_list = selector.xpath('//*[@id="viewlist_ul"]/li/a/@href')
    for each in url_list:
        the_url = 'http://www.che168.com'+each
        self.get_infos(the_url)
    

    可以看到,提取页面中二手车信息的代码是封装到了一个函数中,而这个函数需要传入一个参数,那就是相应的二手车主页URL。

    提取每个主页的信息

    每个主页是一个单独的链接,所以可以写一个函数,传入一个url,然后输出需要提取的信息就行了,具体代码如下:

    def get_infos(self,page_url):
        dic = {}
        html = requests.get(page_url,headers=self.headers).text
        selector = etree.HTML(html)
        car_info = selector.xpath('//div[@class="car-info"]')
        if car_info:
            dic['title'] = car_info[0].xpath('//div[@class="car-title"]/h2/text()')[0]
            price = car_info[0].xpath('//div[@class="car-price"]/ins/text()')[0]
            dic['price'] = price.strip(' ').replace('¥','')
            dic['xslc'] = car_info[0].xpath('//div[@class="details"]/ul/li/span/text()')[0]
            dic['scsp'] = car_info[0].xpath('//div[@class="details"]/ul/li/span/text()')[1]
            dic['dwpl'] = car_info[0].xpath('//div[@class="details"]/ul/li/span/text()')[2]
            dic['city'] = car_info[0].xpath('//div[@class="details"]/ul/li/span/text()')[3]
            dic['call_num'] = car_info[0].xpath("//a[contains(@class,'btn') and contains(@class,'btn-iphone3')]/text()")[0]
            commitment_tag = car_info[0].xpath('//div[@class="commitment-tag"]/ul/li/span/text()')
            dic['commitment_tag'] = '/'.join(commitment_tag)
            dic['address'] = car_info[0].xpath('//div[@class="car-address"]/text()')[0].strip()
            dic['call_man'] = car_info[0].xpath('//div[@class="car-address"]/text()')[-1].strip()
            print(dic)
            self.coll.insert(dic)
    

    在这段代码中,首先创建一个空的字典,然后为了后续存储信息更加方便,因此把所有的信息都放到一个字典中去。

    提取的方式依然是使用xpath选择器,由于有的信息格式不符合之后要保存的格式,所以使用python的基本方法稍微处理了一下。

    最后,再保存信息之前,只用print打印一下提取到信息,查看信息的完整性和准确性。

    这样,一个爬虫的前2步就已经完成了,剩下一的就是选中一个合适的方式将信息储存起来。数据库是个好工具,mongodb更是一个好数据库,没错,就是你了!

    使用数据库保存信息

    连接数据库

    由于爬虫的信息不需要太明确的关系,主要目的是存储信息,所以数据库的选择上优先选择mongodb,这种非关系型数据库真是最好不过了。

    首先需要导入相应的数据库工具库

    from pymongo import MongoClient
    

    然后是连接数据库,由于这个爬虫是写到一个AutohomeSpider类中,因此可以在初始化的时候直接链接指定的数据库,并且可以同时创建表格。

    具体代码如下:

    self.coon = MongoClient('localhost',27017)
    self.coll = self.coon['autohome']['Oldcars']
    

    上述代码可以看到,连接了本地mongodb之后,可以直接创建之前不存在的数据库和数据表。

    存入信息

    mongodb插入信息的方式非常简单,只需要将数据存放到一个字典中,然后使用 insert() 方法就行。

    具体插入信息的代码在上面代码中的

    self.coll.insert(dic)
    

    也就是每爬取一条信息就存入mongodb中。

    最后爬虫结束,可以使用第三方可视化工具查看一下mongodb中存储的数据:

    mongodb

    爬虫源码

    爬虫比较简单,爬取的信息也没有多大的价值,因此不做后续深入研究,这个爬虫主要是为了介绍lxml解析器和Xpath选择器。

    源码:https://github.com/Hopetree/Spiders100/blob/master/autohome.py

  • 相关阅读:
    Good Bye 2014 B. New Year Permutation(floyd )
    hdu 5147 Sequence II (树状数组 求逆序数)
    POJ 1696 Space Ant (极角排序)
    POJ 2398 Toy Storage (叉积判断点和线段的关系)
    hdu 2897 邂逅明下 (简单巴什博弈)
    poj 1410 Intersection (判断线段与矩形相交 判线段相交)
    HDU 3400 Line belt (三分嵌套)
    Codeforces Round #279 (Div. 2) C. Hacking Cypher (大数取余)
    Codeforces Round #179 (Div. 2) B. Yaroslav and Two Strings (容斥原理)
    hdu 1576 A/B (求逆元)
  • 原文地址:https://www.cnblogs.com/gopythoner/p/7801470.html
Copyright © 2011-2022 走看看