当前位置: 首页 > 科技观察

Python模拟登录实战,采集整站表格数据

时间:2023-03-11 22:38:34 科技观察

Python模拟实际登录,收集整个站点的表单数据。!把这个网页上所有县市所有年份所有农作物的数据都爬下来,存到Access里!”我见他可怜巴巴的,不情愿地摆摆手说:“好,我们马上开始!”目标分析大师给了我的网址是这样的:https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg这样打开:根据我学习爬虫的短暂经验,通常只需要附加年月日等参数即可到url,然后用requests.get获取response,解析html就结束了,所以这次应该是一样的——只是需要想办法获取具体的年份、地名、农作物名称first,其他部分只要稍微修改一下之前的代码就可以使用了。没有挑战性的工作,生活真的很无聊。点击ViewSummary后,目标页面出现长条。大表中的数据就是目标数据。它似乎没什么特别的——有问题目标数据所在网页的URL是:https://www.ctic.org/crm/?action=result,刚刚选择的参数不作为URL参数!url和网页都变了,所以不是ajax,和想象中的大不一样。尝试获取target页面让我来康康点击ViewSummary按钮:右键点击ViewSummarycheck:说实话第一次遇到提交任务的任务形式。以前可能是老天眷顾,什么都能搞定,今天终于看到一个帖子。点击ViewSummary,在DevTools中找到网络的第一项:无论发生什么,尝试发布importrequestsurl='https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg'headers={'user-agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)''AppleWebKit/537.36(KHTML,likeGecko)''Chrome/74.0.3729.131Safari/537.36','Host':'www.ctic.org'}data={'_csrf':'SjFKLWxVVkkaSRBYQWYYCA1TMG8iYR8ReUYcSj04Jh4EBzIdBGwmLw==','CRMSearchForm[year]':'2011','CRMSearchForm[format]':'Acres','CRMSearchForm[area]':'County','CRMSearchForm[region]':'Midwest','CRMSearchForm[state]':'IL','CRMSearchForm[county]':'Adams','CRMSearchForm[crop_type]':'All','summary':'county'}response=requests.post(url,data=data,headers=headers)print(response.status_code)不出所料,输出是400……我猜这是传说中的饼干在搞鬼?感到内疚并渴望尝试!首先,我不知道cookies到底是什么。我只知道它们是用来维护会话的。他们应该来自第一个get。我们先看一下:response1=requests.get(url,headers=headers)ifresponse1.status_code==200:cookies=response1.cookiesprint(cookies)output:,]>Nah,看不懂,不看不管,直接把它放到post里试试response2=requests.post(url,data=data,headers=headers,cookies=cookies)print(response2.status_code)还是400,气氛顿时有些紧张,我给你饼干了,你还想要什么?!突然,我发现一件事:我好像在最开始的post请求的数据中看到了可疑的_csrf?cookie中好像有一个完全看不懂的_csrf!但是两个_csrf的值在结构上明显不同。我尝试用cookie中的_csrf替换data中的_csrf,但渐渐有了一个想法:虽然两个_csrf不相等,但应该是Matched的,数据我只是从浏览器来的,而cookies是从python程序来的,所以它不匹配!于是又点了浏览器的DevTools,Ctrl+F搜索。嘿嘿,我发现了:还有这三个地方。第一处下一行的csrf_token显然是post请求带过来的数据中的_csrf,另外两个是js中的函数。虽然js没学好,但是可以看出这两个是通过post请求获取的。姓名和县名,宾果!一次解决两个问题。为了验证我的猜想,我打算在点击ViewSummary前直接使用requests获取该页面的HTML和cookies,并在点击View时将HTML中提取的csrf_token值作为帖子请求的数据中的_csrf值总结,并附上cookies,所以两个_csrf应该匹配:fromlxmlimportetreesponse1=requests.get(url,headers=headers)cookies=response1.cookieshtml=etree.HTML(response1.text)csrf_token=html.xpath('/html/head/meta[3]/@content')[0]data.update({'_csrf':csrf_token})response2=requests.post(url,data=data,headers=headers,cookies=cookies)print(response2.status_code)输出200,虽然和Chrome显示的302不一样,但也代表成功,无视。把response2.text写入html文件,打开看到这个:呀,数据都在!说明我的猜测是对的!然后我会尝试requests.Session(),这个我从来没有用过的,来维护session和自动处理cookies。试试pandas库提取网页表既然已经拿到了目标页面的HTML,那么在获取所有年、地、州、县名之前,先测试一下pandas.read_html提取网页表的功能.编写代码时在IDE自动补全下拉列表中找到了pandas.read_html函数。我一直想尝试一下。今天趁机拔了出来:importpandasaspddf=pd.read_html(response2.text)[0]print(df)输出:耶!明白了,确实比手工提取方便,值字符串自动转为值,优秀!准备所有参数,然后获取所有年份、地区、州名和县名。年份和地区是硬编码在HTML中的,可以直接通过xpath获取:州名和县名应该根据之前找到的两个js函数使用post请求获取。其中州名要根据地区名获取,县名要根据州名获取,设置两个循环即可defnew():session=requests.Session()response=session.get(url=url,headers=headers)html=etree.HTML(response.text)returnssession,htmlsession,html=new()years=html.xpath('//*[@id="crmsearchform-year"]/option/text()')regions=html.xpath('//*[@id="crmsearchform-region"]/option/text()')_csrf=html.xpath('/html/head/meta[3]/@content')[0]region_state={}state_county={}forregioninregions:data={'region':region,'_csrf':_csrf}response=session.post(url_state,data=data)html=etree.HTML(response.json())region_state[region]={x:yforx,yinzip(html.xpath('//option/@value'),html.xpath('//option/text()'))}forstateinregion_state[region]:data={'state':state,'_csrf':_csrf}response=session.post(url_county,data=data)html=etree.HTML(response.json())state_county[state]=html.xpath('//option/@value')啧啧,用requests.Session不需要to完全由您自己管理cookie。方便的!具体获得的州县名我就不公布了,因为太多了。然后将年份、地区、州名、县名所有可能的组合整理成一个csv文件,然后直接从csv中读取并构造post请求的数据字典:remain=[[str(year),str(region),str(state),str(county)]foryearsforregioninregionsforstateinregion_state[region]forcountyinstate_county[state]]remain=pd.DataFrame(remain,columns=['CRMSearchForm[year]','CRMSearchForm[region]','CRMSearchForm[state]]','CRMSearchForm[county]'])remain.to_csv('remain.csv',index=False)#由于州名有缩写和全名,所以本地也保存一份importjsonwithopen('region_state.json','w')asjson_file:json.dump(region_state,json_file,indent=4)我看了一下,一共49473行——也就是说至少要发送49473次post请求才能爬取所有的数据。如果手动获取,则需要点击十次。数的次数...然后开始爬取importpyodbcwithopen("region_state.json")asjson_file:region_state=json.load(json_file)data=pd.read_csv('remain.csv')#读取爬取的cnxn=pyodbc.connect('DRIVER={MicrosoftAccessDriver(*.mdb,*.accdb)};''DBQ=./ctic_crm.accdb')crsr=cnxn.cursor()crsr.execute('selectYear_,Region,State,Countyfromctic_crm')done=crsr.fetchall()done=[list(x)forxindone]done=pd.DataFrame([list(x)forxindone],columns=['CRMSearchForm[year]','CRMSearchForm[region]','CRMSearchForm[state]','CRMSearchForm[county]'])done['CRMSearchForm[year]']=done['CRMSearchForm[year]'].astype('int64')state2st={y:xforzinregion_state.values()forx,yinz.items()}done['CRMSearchForm[state]']=[state2st[x]forxindone['CRMSearchForm[state]']]#排除已删除的remain=data.append(done)remain=remain.drop_duplicates(keep=False)total=len(remain)print(f'{total}left.n')deldata#%%remain['CRMSearchForm[year]']=remain['CRMSearchForm[year]'].astype('str')columns=['Crop','Total_Planted_Acres','Conservation_Tillage_No_Till','Conservation_Tillage_Ridge_Till','Conservation_Tillage_Mulch_Till','Conservation_Tillage_Total','Other_Tillage_Practices_Reduced_Till15_30_Residue','Other_Tillage_Practices_Conventional_Till0_15_Residue']fields=['Year_','Units','Area','Region','State','ColumatMS=[columatmatedata']]':'Acres','CRMSearchForm[area]':'County','CRMSearchForm[crop_type]':'All','summary':'county'}headers={'user-agent':'Mozilla/5.0(WindowsNT10.0;Win64;x64)''AppleWebKit/537.36(KHTML,likeGecko)''Chrome/74.0.3729.131Safari/537.36','Host':'www.ctic.org','Upgrade-Insecure-Requests':'1','DNT':'1','Connection':'keep-alive'}url='https://www.ctic.org/crm?tdsourcetag=s_pctim_aiomsg'headers2=headers.copy()headers2=headers2.update({'Referer':url,'Origin':'https://www.ctic.org'})defnew():session=requests.Session()response=session.get(url=url,headers=headers)html=etree.HTML(response.text)_csrf=html.xpath('/html/head/meta[3]/@content')[0]returnssession,_csrfsession,_csrf=new()for_,rowinremain.iterrows():temp=dict(row)data.update(temp)data.update({'_csrf':_csrf})whileTrue:try:response=session.post(url,data=data,headers=headers2,timeout=15)breakexceptionase:session.close()print(e)print('nSleep30s.n')time.sleep(30)session,_csrf=new()data.update({'_csrf':_csrf})df=pd.read_html(response.text)[0].dropna(how='all')df.columns=columnsdf['Year_']=int(temp['CRMSearchForm[year]'])df['Units']='Acres'df['Area']='County'df['Region']=temp['CRMSearchForm[region]']df['State']=region_state[temp['CRMSearchForm[region]']][temp['CRMSearchForm[state]']]df['County']=temp['CRMSearchForm[county]']df=df.reindex(columns=fields)forrecordindf.itertuples(index=False):tuple_record=tuple(record)sql_insert=f'INSERTIINTOctic_crmVALUES{tuple_record}'sql_insert=sql_insert.replace(',nan,',',null,')crsr.execute(sql_insert)crsr.commit()print(total,row.to_list())total-=1else:print('Done!')crsr.close()cnxn.close()注意这里有atry...except..语句是因为时不时会出现Connectionaborted错误。有的时候9000次会打断一次,有的时候打断一次。这就是为什么我加了readandexcludealreadycrawled的原因,而且我担心被识别为爬虫,我丰富了headers(好像没什么用),每次断开连接,我暂停30s,重新打开一个session,然后打开了一个周末的程序,command我终于打出了Done!在行中,我在Access中看到了816,288条记录,我想:下次试试多线程(进程)和代理池。:“好的”。隔着屏幕都能感受到无尽的敬佩和感激。直到现在,大哥都感动得说不出话来。