查看原文
其他

爬虫实战——聚募网股权众筹信息爬取

数据分析团队 Stata and Python数据分析 2022-03-15

本文作者:陈志玲

文字编辑:余术玲

技术总编:张   邯


相信在本文之前,大家也了解和掌握了很多有关Python爬虫的相关知识。本文以爬取聚募网股权众筹项目的团队信息为例,来介绍时间戳、json标准库在实战中的应用。

使用谷歌浏览器,进入目标网站,选择已完成项目(https://www.dreammove.cn/list/index.html?industry=0&type=8&city=0)。

点击任意一个项目进入第二层——团队的详细信息(https://www.dreammove.cn/project/detail/id/GQ15139949280012975.html)

观察所有项目的url,得出规律,变换部分即为该项目的id。在爬虫的过程中,我们要清楚自己每一步的目的。由此我们爬取的步骤为:先获取第一层的项目信息,由此来获得每个项目的id,然后构建每个项目的url从而爬取第二层的团队信息。
现在我们打开目标网站,按F12(或Fn+F12)键进入开发者模式,查看请求报文可以发现该网站的网页请求方式均是get方法,如下图:

接下来,我们将使用requests、time、json库来实现所需信息的抓取。


一、导入Python标准库和第三方库
这里我们将用到Python的三个库。Requests库是一个第三方库,主要的功能是让人们可以提取网页的内容,通过HTTP协议获取网络上的资源;time库是处理时间的标准库,它提供系统级精确计时器的功能;json是一个标准库,它是一种轻量级的数据交换格式,它将数据以键值对和数值列表的形式有序存储。接下来我们导入这三个库:
import requestsimport timeimport json


二、解析JSON文件获取第一层所有页面的id

接下来我们需要爬取第一层每个页面的项目id。但是在此处,我们发现了谷歌浏览器的一个功能——用谷歌浏览器电脑端模仿手机客户端(如下图),它是通过模仿手机屏幕大小来模仿手机,谷歌浏览器甚至可以设置不同的手机型号(此处感兴趣的读者可自行研究)。为什么我们会用到手机模式呢?因为在我们爬取的过程中发现,在正常的电脑端我们得到的是xml文件,在手机模式下得到的是json文件,而本例中我们选择json格式的数据。

首先,我们想要每一个页面每一个项目的id,那么我们可以先从一个页面开始,尝试获取该页面的所有项目id。
 
我们构建一个名为headers的字典,内容即上图的Headers。并传requests.get()中的headers参数,headers中可以传入请求头的部分信息以防止网站反爬。我们使用几个重要的参数构建字典,如下所示:
headers = {"Accept":"application/json, text/javascript, */*; q=0.01","Referer":"https://www.dreammove.cn/list/index.html?industry=0&type=8&city=0","User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100Mobile Safari/537.36","X-Requested-With":"XMLHttpRequest"}

上图中headers的Request URL:

https://www.dreammove.cn/list/get_list.html?type=8&industry=0&city=0&offset=1&keyword=&_=1566521974870

当我们滚动左边的页面时,可以发现上图右边的headers的Request URL发生了变化,变为:

https://www.dreammove.cn/list/get_list.html?type=8&industry=0&city=0&offset=2&keyword=&_=1566523662637

当我们滚动到最后一页时可发现变化是有规律的。url中变动的第一个数字是对应的页面数,第二串数字是从1970年1月1日0时0分0秒开始计算的,截止到我们看到此网页的时间(此处是以毫秒为单位的)。Python默认情况下的时间戳是以秒为单位输出的float,因此我们需要通过把秒转换成毫秒的方法获得13位的时间戳。

timestamp = int(round(time.time()*1000))
接下来,使用requests.get()方法抓取网页源代码,以第一页爬取为例,程序如下:
url=f"https://www.dreammove.cn/list/get_list.html?type=8&industry=0&city=0&offset=1&keyword=&_={timestamp}"raw_html = requests.get(url,headers= headers)html_text = raw_html.text
此时我们可以查看preview,可以看到我们得到的html_text是一个json文件,而我们只需要其中的id。我们可以使用json.loads()方法,来解析json字符串,返回了一个嵌套字典,而id在以list为键的值中,所以获得id的程序如下所示:
json.loads(html_text)["data"]["list"]

至此,我们就获得了第一个页面所有项目的id,在整个已完成的项目中,我们发现一共有22页,为了获得所有页面id,我们可以将上述四行获取一个页面id信息的程序进行循环。将获得第一页项目id的程序改写为:
ProjectInfo = []for i in range(1,23):url=f"https://www.dreammove.cn/list/get_list.html?type=8&industry=0&city=0&offset={i}&keyword=&_={timestamp}" raw_html = requests.get(url,headers= headers) html_text = raw_html.text project=[] project=json.loads(html_text)["data"]["list"]     ProjectInfo.extend(project)
我们先创建一个空列表ProjectInfo,接下来对页面数进行遍历,并将获得的每一个页面的项目id添加到ProjectInfo列表中。此处我们使用的extend方法。extend和append的区别是:a.append(b),结果是将整个b列表作为列表追加到a列表尾部,a.extend(b),是将b列表的各个元素追加到a列表尾部。最后打印列表ProjectInfo,即所有的项目id。


三、爬取第二层项目的团队信息

由上述的程序,我们获得了有所有项目id的列表ProjectInfo,但此列表中还有其他信息,所以我们要从该列表中提取所有id。这段程序我们将放在文章末尾展示。
接下来我们开始爬取第二层信息。此处与爬取第一层网页信息方法一样从下图中获得url和headers。因为有198个项目,所以对198个id进行遍历,得到198个项目的url,从而获得所有团队信息。

具体程序为:
headers ={"Accept": "application/json, text/javascript, */*;q=0.01","Accept-Encoding":"gzip, deflate, br","Accept-Language":"zh-CN,zh;q=0.9","Connection":"keep-alive","Cookie":"PHPSESSID=m2el8qb3r83d3u6f6hvashqd85;Hm_lvt_c18b08cac9b94bf4628c0277d3a4d7de=1566483953,1566521937,1566607126,1566630977;Hm_lpvt_c18b08cac9b94bf4628c0277d3a4d7de=1566631899","Host": "www.dreammove.cn","Referer":"https://www.dreammove.cn/project/detail/id/GQ15639333500017933.html","User-Agent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36(KHTML, like Gecko)Chrome/76.0.3809.100 MobileSafari/537.36","X-Requested-With":"XMLHttpRequest"}timestamp =int(round(time.time()*1000))teamInfo=[]for b in range(0,198): url=f"https://www.dreammove.cn/project/project_team/id/{ProjectId[b]}?_={timestamp}" raw_html = requests.get(url,headers =headers) html_text=raw_html.text    teamInfoi=json.loads(html_text)["data"]["team_list"]
然后对得到的json文件进行解析,我们只需要以team_list为键的内容,接下来就是将每一次遍历得到的team_list的内容进行拼接,但此处使用前文中提到的extend方法时程序就会报错,显示布尔对象不可迭代:

这说明我们拼接的对象里有不是列表的对象,于是需要对拼接内容进行一次判断:是列表就拼接,不是就中断此次循环,进入下一次循环。最后输出的teamInfo就是我们所需的所有项目的团队信息。
if teamInfoi.__class__ ==list: teamInfo=teamInfo+teamInfoi;continueprint(teamInfo)
为了让每个人的信息显示的更醒目,我们可以再加最后一步,将列表teamInfo中的每一个元素进行遍历输出即可。
for var in teamInfo: print(var)
注:当我们运行所有程序时,为了只输出最终结果,除了最后一个print(var),其余print我们可以选择注释掉,即在print前加#。
完整程序:
import requestsimport time import json headers = {"Accept": "application/json, text/javascript, */*;q=0.01", "Referer":"https://www.dreammove.cn/list/index.html?industry=0&type=8&city=0", "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100Mobile Safari/537.36", "X-Requested-With": "XMLHttpRequest"}timestamp = int(round(time.time()*1000))ProjectInfo = []for i in range(1,23):   url=f"https://www.dreammove.cn/list/get_list.html?type=8&industry=0&city=0&offset={i}&keyword=&_={timestamp}" raw_html = requests.get(url,headers= headers) html_text = raw_html.text project=json.loads(html_text)["data"]["list"] ProjectInfo.extend(project)#print(ProjectInfo)Project_FileName = "C:\\CrowdFunding\\dreammove\\ProjectInfo.csv"VarName =['id','update_time','province_name','subsite_id','is_open','industry','type','open_flag','project_name','step','seo_string','abstract','cover','project_phase','member_count','province','city','address','company_name','project_url','uid','over_time','vote_leader_step','stage','is_agree','is_del','agreement_id','barcode','sort','display_subsite_id','need_fund','real_fund','project_valuation','final_valuation','min_lead_fund','min_follow_fund','total_fund','agree_total_fund','leader_flag','leader_id','read_cnt','follow_cnt','inverstor_cnt','comment_cnt','nickname','short_name','site_url','site_logo','storelevel','industry_name']with open(Project_FileName,"w",encoding = "gb18030") as f: # f.write("\t".join(VarName)+"\n") for EachInfo in ProjectInfo: tempInfo = [] for key in VarName: if key in EachInfo: tempInfo.append(str(EachInfo[key]).replace("\n","").replace("\t","").replace("\r","")) else: tempInfo.append("")            f.write("\t".join(tempInfo)+"\n")with open(Project_FileName,"r",encoding = "gb18030") as f: final_Info = f.readlines() ProjectId = [] for i in range(1,len(final_Info)): ProjectId.append(final_Info[i].split("\t")[0])#print(final_Info)#print(ProjectId)headers = {"Accept":"application/json, text/javascript, */*; q=0.01", "Accept-Encoding":"gzip, deflate, br", "Accept-Language":"zh-CN,zh;q=0.9", "Connection":"keep-alive", "Cookie":"PHPSESSID=m2el8qb3r83d3u6f6hvashqd85;Hm_lvt_c18b08cac9b94bf4628c0277d3a4d7de=1566483953,1566521937,1566607126,1566630977;Hm_lpvt_c18b08cac9b94bf4628c0277d3a4d7de=1566631899", "Host": "www.dreammove.cn", "Referer":"https://www.dreammove.cn/project/detail/id/GQ15639333500017933.html", "User-Agent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36(KHTML, like Gecko) Chrome/76.0.3809.100 Mobile Safari/537.36", "X-Requested-With":"XMLHttpRequest"}timestamp = int(round(time.time()*1000))teamInfo=[]for b in range(0,198):   url=f"https://www.dreammove.cn/project/project_team/id/{ProjectId[b]}?_={timestamp}" raw_html = requests.get(url,headers = headers) html_text=raw_html.text teamInfoi=json.loads(html_text)["data"]["team_list"] if teamInfoi.__class__ == list: teamInfo.extend(teamInfoi);continuefor var in teamInfo: print(var)

对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!

  类型内置函数-type() isinstance()

duplicates drop之前,我们要做什么?

数据含义记不住?—— label“大神”来帮忙

实战演练-如何获取众筹项目的团队信息

Zipfile(一)

tabplot命令

Jupyter Notebook不为人知的秘密

字符串方法(三)

数据,我要“拷打”你

花式调用返回值 —— svret与storedresults
encode 和decode——带你探索编码与解码的世界
字符串方法(二)
如何快速生成分组变量?
用Stata实现数据标准化
字符串方法介绍
Stata16新功能之“框架”——frlink连接多个数据集(3)
Stata16新功能之“框架”——基础命令大合集(2)
三分钟教你读懂Python报错
解析XML文件

关于我们

微信公众号“Stata and Python数据分析”分享实用的Stata、Python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。

此外,欢迎大家踊跃投稿,介绍一些关于Stata和Python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com
投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存