功能:

通过程序实现从基金列表页,获取指定页数内所有基金的近一周收益率以及每支基金的详情页链接。再进入每支基金的详情页获取其余的基金信息,将所有获取到的基金详细信息按近6月收益率倒序排列写入一个Excel表格。

思路:

  1. 通过实例化Tiantian_spider类的对象,初始化一个PhantomJS浏览器对象
  2. 使用浏览器对象访问天天基金近六月排行的页面,获取该页面的源码
  3. 从源码从获取每支基金所在的行(可以指定要获取基金的页数)

python爬取天天基金特色数据_程序人生

  1. 从每行中获取每支基金的近1周收益率和基金详情链接

python爬取天天基金特色数据_单元测试_02

  1. 获取到每个基金的详情链接后,使用多进程分别进入每支基金的详情页面
  2. 进入详情页后,获取基金的相关信息,并存入列表

python爬取天天基金特色数据_python_03

  1. 将从所有基金的基金详情与在列表页获取的基金近1周收益率拼接后存入列表
  2. 再将所有信息写入Excel表格

python爬取天天基金特色数据_单元测试_04

from selenium import webdriver
from lxml import etree
import time
from openpyxl import Workbook
import multiprocessing
import re

class Tiantian_spider():
   def __init__(self):
       self.driver = webdriver.PhantomJS()      #指定的PhantomJS浏览器创建浏览器对象
       self.html = None
       self.next_page = True
       self.fund_url_list = []


    #1 发起请求
   def parser_url(self):
       # if self.next_page :
       # 点击页面进行翻页
       # label[last()]---》定位到最后一个label,即<label value="xx">下一页</label>
       # last()是一个函数,表示取最后一个
       self.driver.find_element_by_xpath("//div[@id='pagebar']/label[last()]").click()
       time.sleep(4)  # 网页返回数据需要时间
       self.html = self.driver.page_source


   def parser_data_for_url(self):
       '''从基金列表页获取每支基金的近一周收益和详情链接'''
       # 解析字符串格式的HTML文档对象,将传进去的字符串转变成_Element对象
html = etree.HTML(self.html)
       tr_list = html.xpath("//table[@id ='dbtable']//tbody/tr")
       next_page = html.xpath("//div[@id ='pagebar']//label[last()]")
       for tr in tr_list:
           tds =tr.xpath("./td")
           # 将近一周收益和详情链接组成的元组加入fund_url_list列表
           self.fund_url_list.append((str(tds[8].text),str(tds[2].xpath("./a/@href")[0])))

       return next_page    # 返回下一页

    #翻页控制器
   def over_page(self,next_page):
       # 获取最后一页
       kw = next_page[0].xpath("./label[contains(@class,'end')]")
       # print(kw)
       # 判断是否是最后一页,如果是,则返回False,否则返回True
       flag = True if len(kw)==0 else False
       return flag

   def get_every_fund_url(self, url, page):
       # page:要获取前多少页的基金数据
       # 1 发起请求
       # 2 获取数据,解析数据
       self.driver.get(url)
       self.html = self.driver.page_source
       # 当页数不为0且还有下一页时,执行下面的操作
       while page > 0 and self.next_page:
           next_page= self.parser_data_for_url()
           # 4 翻页继续爬取
           self.next_page = self.over_page(next_page)
           # 如果不是下一页,就继续翻页
           if self.next_page:
                self.parser_url()
           page -= 1
       # 返回每支基金近一周收益和详情链接
       return self.fund_url_list


   def close_driver(self):
       self.driver.quit()


def save_data(data):

   wb = Workbook()     # 新创建一个文件
   ws = wb.active                 # 获取当前正在运行的工作表/激活工作表
    #将数据一行一行插入到工作表中
    #列表第一个元素将作为标题
   for i in data:
       ws.append(i)
   wb.save("近6月基金排名_" + time.strftime('%Y%m%d%H%M%S')+".xlsx")


# 多进程任务函数
# 获取进入基金的详情页获取详细信息
def run(url, nearly_1_week):

   driver = webdriver.PhantomJS()
   driver.get(url)    
   page_html = etree.HTML(driver.page_source)  # 获取页面源码
    #获取基金名称
    #通过xpath或者的是一个元素列表,要元素下面的子元素,需要取某个具体的元素,不能用列表取
   fund_name =page_html.xpath("//div[@class='fundDetail-tit']/div/text()")[0]
    #获取基金代码类名
   fund_code_class_name = page_html.xpath("//div[@class='fundDetail-tit']/div/span[last()]/@class")[0]
    #根据代码类名判断基金代码只有一个,还是有前后端两个
   if fund_code_class_name == "ui-num":
       fund_code = page_html.xpath("//div[@class='fundDetail-tit']/div/span[last()]/text()")[0]
   elif fund_code_class_name == "fundcodeInfo":
       fund_code_info = page_html.xpath("//div[@class='fundDetail-tit']/div/span[@class='fundcodeInfo']")[0]
       fund_code =  "前端: " +fund_code_info.xpath("./span[1]/text()")[0] + "    后端: " + fund_code_info.xpath("./span[2]/text()")[0]

    #收益和净值所在的上层div
   data_of_fund = page_html.xpath("//div[@class='dataOfFund']")[0]
    #近1月
   nearly_1_month =data_of_fund.xpath("./dl[1]/dd[2]/span[last()]/text()")[0]
    #近1年
   nearly_1_year =data_of_fund.xpath("./dl[1]/dd[3]/span[last()]/text()")[0]
    #日期
   date = data_of_fund.xpath("./dl[2]/dt/p/text()")[0]
   date = re.findall(r"(\d{4}-\d{2}-\d{2})", date)[0] ifre.findall(r"(\d{4}-\d{2}-\d{2})", date) else ""
    #单位净值
   unit_net_worth =data_of_fund.xpath("./dl[2]/dd[1]/span[1]/text()")[0]
    #日增长率
   daily_growth_rate =data_of_fund.xpath("./dl[2]/dd[1]/span[2]/text()")[0]
    #近3月
   nearly_3_month =data_of_fund.xpath("./dl[2]/dd[2]/span[last()]/text()")[0]
    #近3年
   nearly_3_year =data_of_fund.xpath("./dl[2]/dd[3]/span[last()]/text()")[0]
    #累计净值
   accumulated_net =data_of_fund.xpath("./dl[3]/dd[1]/span[1]/text()")[0]
    #近6月
   nearly_6_month =data_of_fund.xpath("./dl[3]/dd[2]/span[last()]/text()")[0]
    #基金成立日
   since_established =data_of_fund.xpath("./dl[3]/dd[3]/span[last()]/text()")[0]
    #获取基金类型,风险程度,规模等信息所在的上层vid
   fund_info_item =page_html.xpath("//div[@class='infoOfFund']")[0]
    #获取基金的类型以及风险程度
   fund_type = fund_info_item.xpath(".//tr[1]/td[1]/a/text()")[0]
   fund_risk =fund_info_item.xpath(".//tr[1]/td[1]/text()")[1].split()[-1].strip()
    #获取基金规模
    fund_scale =fund_info_item.xpath(".//tr[1]/td[2]/text()")[0].split(":")[-1]
    #获取基金经理
   fund_manager =fund_info_item.xpath(".//tr[1]/td[3]/a/text()")[0]
    #获取基金成立日
   establishment_date =fund_info_item.xpath(".//tr[2]/td[1]/text()")[0].split(":")[-1]
    #获取管理人
   administrator =fund_info_item.xpath(".//tr[2]/td[2]/a/text()")[0]
    #获取评级类名
   fund_rating_class_name =fund_info_item.xpath(".//tr[2]/td[3]/div/@class")[0]
   data_list = []
    #将每支基金的详细信息拼接成一个列表,并返回
   data_list.append(str(fund_code))
   data_list.append(str(fund_name))
   data_list.append(str(date))
   data_list.append(str(unit_net_worth))
   data_list.append(str(accumulated_net))
   data_list.append(str(daily_growth_rate))
   data_list.append(str(nearly_1_week))
   data_list.append(str(nearly_1_month))
   data_list.append(str(nearly_3_month))
   data_list.append(str(nearly_6_month))
   data_list.append(str(nearly_1_year))
   data_list.append(str(nearly_3_year))
   data_list.append(str(since_established))

    #根据评级所在divid的类名判断当前基金是几星
   if fund_rating_class_name == 'jjpj1':
       data_list.append("一星")
   elif fund_rating_class_name == "jjpj2":
       data_list.append("二星")
   elif fund_rating_class_name == "jjpj3":
       data_list.append("三星")
   elif fund_rating_class_name == "jjpj4":
       data_list.append("四星")
   elif fund_rating_class_name == "jjpj5":
       data_list.append("五星")
   else:
       data_list.append("暂无评级")

   data_list.append(fund_type + " | " + fund_risk)
   data_list.append(str(fund_scale))
   data_list.append(str(fund_manager))
   data_list.append(str(establishment_date))
   data_list.append(str(administrator))

    driver.quit()   # 关闭浏览器
   return data_list 


if __name__ == '__main__':

   start = time.time()
    #基金排行按近6月排行页面url
   url ="http://fund.eastmoney.com/data/fundranking.html#tall;c0;r;s6yzf;pn50;ddesc;qsd20200725;qed20210725;qdii;zq;gg;gzbd;gzfs;bbzt;sfbb"
   tiantian = Tiantian_spider()    # 实例化Tiantian_spider类
    #获取每个基金的近1周收益和基金详情链接
    #传入参数为排行页面url和要获取数据的总页数
    url_list = tiantian.get_every_fund_url(url,4)

    #要获取的数据,也作为保存excel的标题
   data = [["基金代码", "基金简称", "日期", "单位净值", "累计净值", "日增长率", "近一周", "近1月", "近3月", "近6月", \
       "近1年", "近3年", "成立来", "基金评级", "基金类型", "基金规模", "基金经理", "成立日", "管理人"]]

   result = []
    #multiprocessing.cpu_count():获取cpu核数
    #新建一个进程池,最大放cpu核数个进程
   pool = multiprocessing.Pool(multiprocessing.cpu_count())
   for nearly_1_week, url in url_list:
       # pool.apply_async:异步执行,10个任务同时执行
       # 通过进程池来执行并发任务
       # 进程池会自动找不同个数的进程来执行任务函数run, 将args=(url, nearly_1_week)中的url, nearly_1_week两个参数传入run函数
       # .get()表示获取任务函数的返回值,即基金的详细信息
       result.append(pool.apply_async(func=run, args=(url,nearly_1_week)).get())

   pool.close()    # 关闭进程池
   pool.join()     # 阻塞进程,所有进程池中的任务都执行完毕了,才能继续执行主进程
    #将基金列表按基金的近6月收益率倒序排列后加入data
   data.extend(sorted(result, key=lambda x:x[9], reverse=True))   
   save_data(data)
   end = time.time()
   print("耗时为:%s秒" % (end - start))