Python 爬虫实战计划:第二周作业

爬取北京赶集网——二手市场——所有商品类目的个人类别的商品信息

最近工作上一直比较忙,拖到现在才写完作业,实在是太不好意思了。
统计了一下,总共爬取了 大概三万条数据,每一页有很多的重复的数据。
流程:
1. 获得所有栏目的url
2.获得每个栏目的所有商品链接
2.1:建立栏目对应的抓取股则
3.获得每个商品的详细信息
具体分析:
1. 获得所有栏目的url
a. 栏目的url在 'dl.fenlei > dt > a' 路径下
b. 剔除 手机号码栏目,里面什么也没有
2. 获得每个栏目的第一页商品链接
a. 建立商品链接抓取规则:
经过测试一共有三种抓取规则:
i. 规则1
1) 'div.infocon tr.zzinfo td.t a.t'大部分栏目都是这个规则
ii. 规则2
1) 'a.ft-tit'
2) 栏目:设备办公用品,农用品,闲置礼品,虚拟物品
iii. 规则3:
1) 'div.layoutlist div.infor01 > a'
2) 栏目:杂七杂八,免费赠送,物品交易
其余均为规则1
b. 将抓取到的商品url存储到数据库中:
3. 获得每个栏目的每一页的商品链接:
a. 经过观察发现:
i. 每个栏目的第一页均为: ‘http://bj.ganji.com/栏目url/’
ii. 除了第一页的每一页均为:‘http://bj.ganji.com/栏目/o页码/'
iii. 页码均已 2 开始,表示第二页
b. 边界检查:
i. 爬取到最后一页,则停止爬取
ii. 经过观察发现,每个栏目的每一商品列表页,有三种边界形式
1) 最后一页的下一页,后有一个 空商品列表的页面。
2) 最后一页的下一页,会有一些过时数据(商品)列表页面
第一条形式,我们可以检测,页面商品页面列表是否为空,来进行边界判断。
第二条形式,就不好判断了。因为最后一页的下一页仍然会有商品列表,只是过时数据,但并不是我们想要的。
iii. so,需要另外一种边界判断形式:
1) 我们发现,正常页面每一页都有一个 下一页 的标签,最后一页的下一页,则没有 下一页标签
2) 有一些另外的:
a) 最有一页没有下一页标签,最后一页的下一页,要么是空商品列表页,要么是过时,商品列表页。
b) so,为了简单处理。我们决定,把这个类型的最后一页pass掉,
4. 获得每页的商品信息:
商品信息页面共两种页面:
1. 转转网
1) 获得:标题,价格,类型
2. 赶集网
1) 获得:标题,价格,类型,地域。
实现:
具体分三个实现,一个MongoDB配置文件:
1. 页面链接爬取器:爬取所有页面中的所有商品链接
2. 页面详情爬取器:爬取所有商品链接页面的商品信息。
3. 主程序:入口程序,调用链接爬取器,详情爬取器。
4.MongoDB配置文件
以下为具体代码:
MongoDB配置文件:

#coding=utf-8
"""mongoDB的相关配置"""

from pymongo import MongoClient
client = MongoClient('localhost',27017)
db = client['ganji']
#商品url
bj_item_urls_collenction = db['bj_item_urls_collenction']
#每一页的页码url
bj_page_urls_collenction = db['bj_page_urls_collenction']
#每个商品页码信息
bj_page_info_collenction = db['bj_page_info_collenction']

链接爬取器:

#coding=utf-8
import time
import requests
import mongoConfig
from bs4 import BeautifulSoup

bj_item_urls_collenction = mongoConfig.bj_item_urls_collenction
bj_page_urls_collenction = mongoConfig.bj_page_urls_collenction

def get_html(url):
    """获得html源码"""
    html = None
    headers = {
        'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                       'Chrome/54.0.2840.87 Safari/537.36')
    }
    #request并不会主动抛出http status code 异常,也就是说 404 之类的
    #所有我们只需要捕捉超时异常即可
    try:
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            html = response.text
            print '下载--',url
        else:
            print 'HTTP状态码不对',response.status_code,url
    except requests.Timeout as e:
        print '访问超时 --- ',url
    time.sleep(3)
    return html

def get_all_category_links(url):
    """获得所有栏目的url"""
    html = get_html(url)
    soup = BeautifulSoup(html, 'lxml')
    base_url = 'http://bj.ganji.com{}'
    dl = soup.select('dl.fenlei > dt > a')
    links = None
    if dl:
        links = [base_url.format(link.get('href')) for link in dl]
    else:
        print '栏目信息不存在',url
    if 'http://bj.ganji.com/shoujihaoma/' in links:
        links.remove('http://bj.ganji.com/shoujihaoma/')
        print '删除 http://bj.ganji.com/shoujihaoma/'
    return links

def get_item_links(url):
    """获得当前页商品列表的url"""
    html = get_html(url)
    soup = BeautifulSoup(html, 'lxml')
    #三种抓取规则
    item_rules = ['div.infocon tr.zzinfo td.t a.t','a.ft-tit','div.layoutlist div.infor01 > a']
    #a.next 下一页 标签
    if soup.find('a', 'next'):
        for item_rule in item_rules:
            items = soup.select(item_rule)
            if items:
                return [link.get('href').split('?')[0] for link in items]
    return None

def save_to_db(infos):
    """保存到数据库"""
    start = bj_item_urls_collenction.find().count()
    for url in infos:
        if not bj_item_urls_collenction.find_one({'url': url}):
            bj_item_urls_collenction.insert_one({'url': url})
            #print '插入数据库成功 {}'.format(url)
        else:
            pass
            #print '当前商品已经抓取过 {} '.format(url)
    end = bj_item_urls_collenction.find().count()
    print '当前页有 {} 条数据'.format(len(infos))
    print '保存 {} 条数据到数据库'.format(end-start)

def process_main(url):
    """抓取当前栏目的每一页的所有连接"""
    def page_zhuaqu(url):
        is_continue = True
        if not bj_page_urls_collenction.find_one({'url': url}):
            links = get_item_links(url)
            if links:
                save_to_db(links) #保存商品列表到url
                bj_page_urls_collenction.insert_one({'url': url})
                is_continue = True
                print '抓取 {} 页成功'.format(url)
            else:
                print '页面不存在,到达边界', url
                is_continue = False
        else:
            print '当前 {} 页已经抓取过'.format(url)
        return is_continue

    #抓取第一页
    page_zhuaqu(url)
    #抓取其余的页
    page = 2
    is_continue = True
    base_url = url + 'o{}/'
    while is_continue:
        page_url = base_url.format(page)
        is_continue = page_zhuaqu(page_url)
        page += 1

页面详情爬取器:

#coding=utf-8
import time
import requests
import mongoConfig
from bs4 import BeautifulSoup

bj_item_urls_collenction = mongoConfig.bj_item_urls_collenction
bj_page_info_collenction = mongoConfig.bj_page_info_collenction

def get_html(url):
    """获得html源码"""
    html = None
    headers = {
        'User-Agent': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                       'Chrome/54.0.2840.87 Safari/537.36')
    }
    #request并不会主动抛出http status code 异常,也就是说 404 之类的
    #所有我们只需要捕捉超时异常即可
    try:
        response = requests.get(url,headers=headers)
        if response.status_code == 200:
            html = response.text
            print '下载--',url
        else:
            print 'HTTP状态码不对',response.status_code,url
    except requests.Timeout as e:
        print '访问超时 --- ',url
    time.sleep(3)
    return html

def price_to_float(price_tag):
    """价格转换为float格式
        价格 有三种格式:数字型:200     直接转换为数字
                      数字+字符型:1.2万 将万转换为元 12000
                      字符型:面议    不做修改
    """
    price = None
    if price_tag:
        price_str = price_tag[0].get_text()
        if price_str == u'面议':
            price = price_str
        elif price_str.endswith(u'万'):
            price = float(price_str[:-2]) * 10000
        else:
            price = float(price_str)
    return price

def zhuanzhuan_page_parse(soup,url):
    """转转二手交易页面,解析
        获得:标题,价格,地域"""
    area = soup.select('div.palce_li > span > i')
    title = soup.select('h1.info_titile')
    price = soup.select('span.price_now > i')

    data = {'url':url,'title': title[0].get_text() if title else None,
            'price': price_to_float(price),
            'area':area[0].get_text() if area else  None
            }
    return data

def ganji_page_parse(soup,url):
    """赶集网二手交易页面解析
        获得:标题,类型,价格,地域"""
    title= soup.select('h1.title-name')[0].get_text()

    item_soup = soup.select('ul.det-infor > li')
    item_type = item_soup[0].find('a').get_text()
    price = price_to_float(item_soup[1].select('i'))
    area_soup = item_soup[2].find_all('a')
    # BeautifulSoup返回的都是unicode格式的字符串,所以使用unicode格式的字符串来格式化,避免编码问题。
    area = u'{}-{}'.format(area_soup[0].string.strip(), area_soup[1].string.strip())

    data = {
        'url':url,
        'title':title,
        'tiem_type':item_type,
        'price':price,
        'area':area
    }
    return data

def process_main(url):
    """页面解析,并存储"""
    if not bj_page_info_collenction.find_one({'url':url}):
        html = get_html(url)
        soup = BeautifulSoup(html, 'lxml')
        data = None
        if url.startswith('http://bj.ganji.com'):
            data = ganji_page_parse(soup,url)
        elif url.startswith('http://zhuanzhuan.ganji.com'):
            data = zhuanzhuan_page_parse(soup,url)
        bj_page_info_collenction.insert_one(data)
        print '保存数据库成功 {}'.format(url)
    else:
        print '页面已经抓取过 {}'.format(url)

主程序:

#coding=utf-8
import multiprocessing
import mongoConfig
import links_spider
import pages_spider

if __name__ == '__main__':
    #先抓取所有商品连接,保存数据库
    url = 'http://bj.ganji.com/wu/'
    links_urls = links_spider.get_all_category_links(url)
    #未设置进程数,则默认调用cpu个数个进程
    pool = multiprocessing.Pool()
    pool.map_async(links_spider.process_main,links_urls)
    pool.close()
    pool.join()

    #再从数据库中抓取所有商品连接,并解析页面信息
    items_urls =[items['url'] for items in mongoConfig.bj_item_urls_collenction.find({})]

    pool = multiprocessing.Pool()
    pool.map_async(pages_spider.process_main,items_urls)
    pool.close()
    pool.join()

    print '总共保存 {} 条商品url'.format(mongoConfig.bj_item_urls_collenction.find().count())
    print '总共保存 {} 条商品信息'.format(mongoConfig.bj_page_info_collenction.find().count())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容