一名合格的數據分析師分享Python網絡爬蟲二三事(綜合實戰案例)
接上篇文章《一名合格的數據分析師分享Python網絡爬蟲二三事》
五、綜合實戰案例
1. 爬取靜態網頁數據
(1)需求
爬取豆瓣網出版社名字并分別存儲到excel、txt與MySQL數據庫中。
(2)分析
- 查看源碼
- Ctrl+F搜索任意出版社名字,如博集天卷
- 確定正則模式
- "<div class="name">(.*?)</div>"
(3)思路
- 下載目標頁面
- 正則匹配目標內容
- Python列表存儲
- 寫入Excel/txt/MySQL
(4)源碼
- ''信息存儲'''import urllibimport reimport xlsxwriterimport MySQLdb#-----------------(1)存儲到excel與txt-------------------------#def gxls_concent(target_url,pat):
- '''
- 功能:爬取數據
- @target_url:爬取目標網址
- @pat:數據過濾模式
- '''
- data = urllib.request.urlopen(target_url).read()
- reret_concent = re.compile(pat).findall(str(data,'utf-8'))
- return ret_concentdef wxls_concent(ret_xls,ret_concent):
- '''
- 功能:將最終結果寫入douban.xls中
- @ret_xls:最終結果存儲excel表的路徑
- @ret_concent:爬取數據結果列表
- '''
- # 打開最終寫入的文件
- wb1 = xlsxwriter.Workbook(ret_xls)
- # 創建一個sheet工作對象
- ws = wb1.add_worksheet()
- try:
- for i in range(len(ret_concent)):
- data = ret_concent[i]
- ws.write(i,0,data)
- wb1.close()
- except Exception as er:
- print('寫入“'+ret_xls+'”文件時出現錯誤')
- print(er) def wtxt_concent(ret_txt,ret_concent):
- '''
- 功能:將最終結果寫入douban.txt中
- @ret_xls:最終結果存儲excel表的路徑
- @ret_concent:爬取數據結果列表
- '''
- fh = open(ret_txt,"wb")
- try:
- for i in range(len(ret_concent)):
- data = ret_concent[i]
- datadata = data+"\r\n"
- datadata = data.encode()
- fh.write(data)
- except Exception as er:
- print('寫入“'+ret_txt+'”文件時出現錯誤')
- print(er)
- fh.close()def mainXlsTxt():
- '''
- 功能:將數據存儲到excel表中
- '''
- target_url = 'https://read.douban.com/provider/all' # 爬取目標網址
- pat = '<div>(.*?)</div>' # 爬取模式
- ret_xls = "F:/spider_ret/douban.xls" # excel文件路徑
- ret_txt = "F:/spider_ret/douban.txt" # txt文件路徑
- ret_concent = gxls_concent(target_url,pat) # 獲取數據
- wxls_concent(ret_xls,ret_concent) # 寫入excel表
- wtxt_concent(ret_txt,ret_concent) # 寫入txt文件 #---------------------END(1)--------------------------------##-------------------(2)存儲到MySQL---------------------------#def db_con():
- '''
- 功能:連接MySQL數據庫
- '''
- con = MySQLdb.connect(
- host='localhost', # port
- user='root', # usr_name
- passwd='xxxx', # passname
- db='urllib_data', # db_name
- charset='utf8',
- local_infile = 1
- )
- return con def exeSQL(sql):
- '''
- 功能:數據庫查詢函數
- @sql:定義SQL語句
- '''
- print("exeSQL: " + sql)
- #連接數據庫
- con = db_con()
- con.query(sql) def gdb_concent(target_url,pat):
- '''
- 功能:轉換爬取數據為插入數據庫格式:[[value_1],[value_2],...,[value_n]]
- @target_url:爬取目標網址
- @pat:數據過濾模式
- '''
- tmp_concent = gxls_concent(target_url,pat)
- ret_concent = []
- for i in range(len(tmp_concent)):
- ret_concent.append([tmp_concent[i]])
- return ret_concentdef wdb_concent(tbl_name,ret_concent):
- '''
- 功能:將爬取結果寫入MySQL數據庫中
- @tbl_name:數據表名
- @ret_concent:爬取數據結果列表
- '''
- exeSQL("drop table if exists " + tbl_name)
- exeSQL("create table " + tbl_name + "(pro_name VARCHAR(100));")
- insert_sql = "insert into " + tbl_name + " values(%s);"
- con = db_con()
- cursor = con.cursor()
- try:
- cursor.executemany(insert_sql,ret_concent)
- except Exception as er:
- print('執行MySQL:"' + str(insert_sql) + '"時出錯')
- print(er)
- finally:
- cursor.close()
- con.commit()
- con.close()def mainDb():
- '''
- 功能:將數據存儲到MySQL數據庫中
- '''
- target_url = 'https://read.douban.com/provider/all' # 爬取目標網址
- pat = '<div>(.*?)</div>' # 爬取模式
- tbl_name = "provider" # 數據表名
- # 獲取數據
- ret_concent = gdb_concent(target_url,pat)
- # 寫入MySQL數據庫
- wdb_concent(tbl_name,ret_concent) #---------------------END(2)--------------------------------#if __name__ == '__main__':
- mainXlsTxt()
- mainDb()
(5)結果
2. 爬取基于Ajax技術網頁數據
(1)需求
爬取拉勾網廣州的數據挖掘崗位信息并存儲到本地Excel文件中
(2)分析
a. 崗位數據在哪里?
- 打開拉勾網==》輸入關鍵詞“數據挖掘”==》查看源碼==》沒發現崗位信息
- 打開拉勾網==》輸入關鍵詞“數據挖掘”==》按F12==》Network刷新==》按下圖操作
我們可以發現存在position和company開頭的json文件,這很可能就是我們所需要的崗位信息,右擊選擇open link in new tab,可以發現其就是我們所需的內容。
b. 如何實現翻頁?
我們在寫爬蟲的時候需要多頁爬取,自動模擬換頁操作。首先我們點擊下一頁,可以看到url沒有改變,這也就是Ajax(異步加載)的技術。點擊position的json文件,在右側點擊Headers欄,可以發現***部有如下內容:
當我們換頁的時候pn則變為2且first變為false,故我們可以通過構造post表單進行爬取。
c. Json數據結構怎么樣?
(3)源碼
- import urllib.requestimport urllib.parseimport socketfrom multiprocessing.dummy import Poolimport jsonimport timeimport xlsxwriter#----------------------------------------------------------#######(1)獲取代理IP###def getProxies():
- '''
- 功能:調用API獲取原始代理IP池
- '''
- url = "http://api.xicidaili.com/free2016.txt"
- i_headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0"}
- global proxy_addr
- proxy_addr = []
- try:
- req = urllib.request.Request(url,headers = i_headers)
- proxy = urllib.request.urlopen(req).read()
- proxyproxy = proxy.decode('utf-8')
- proxyproxy_addr = proxy.split('\r\n') #設置分隔符為換行符
- except Exception as er:
- print(er)
- return proxy_addr def testProxy(curr_ip):
- '''
- 功能:利用百度首頁,逐個驗證代理IP的有效性
- @curr_ip:當前被驗證的IP
- '''
- socket.setdefaulttimeout(5) #設置全局超時時間
- tarURL = "https://www.baidu.com/" #測試網址
- proxy_ip = []
- try:
- proxy_support = urllib.request.ProxyHandler({"http":curr_ip})
- opener = urllib.request.build_opener(proxy_support)
- opener.addheaders=[("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0")]
- urllib.request.install_opener(opener)
- res = urllib.request.urlopen(tarURL).read()
- proxy_ip.append(curr_ip)
- print(len(res))
- except Exception as er:
- print("驗證代理IP("+curr_ip+")時發生錯誤:"+er)
- return proxy_ip def mulTestProxies(proxies_ip):
- '''
- 功能:構建多進程驗證所有代理IP
- @proxies_ip:代理IP池
- '''
- pool = Pool(processes=4) #開啟四個進程
- proxies_addr = pool.map(testProxy,proxies_ip)
- pool.close()
- pool.join() #等待進程池中的worker進程執行完畢
- return proxies_addr#----------------------------------------------------------#######(2)爬取數據###def getInfoDict(url,page,pos_words_one,proxy_addr_one):
- '''
- 功能:獲取單頁職位數據,返回數據字典
- @url:目標URL
- @page:爬取第幾頁
- @pos_words_one:搜索關鍵詞(單個)
- @proxy_addr_one:使用的代理IP(單個)
- '''
- global pos_dict
- page = 1
- i_headers=("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.22 Safari/537.36 SE 2.X MetaSr 1.0")
- proxy = urllib.request.ProxyHandler({"http":proxy_addr_one})
- opener = urllib.request.build_opener(proxy,urllib.request.HTTPHandler)
- opener.addheaders=[i_headers]
- urllib.request.install_opener(opener)
- if page==1:
- tORf = "true"
- else:
- tORf = "false"
- mydata = urllib.parse.urlencode({"first": tORf,
- "pn": page, #pn變化實現翻頁
- "kd": pos_words_one } ).encode("utf-8")
- try:
- req = urllib.request.Request(url,mydata)
- data=urllib.request.urlopen(req).read().decode("utf-8","ignore") #利用代理ip打開
- pos_dict = json.loads(data) #將str轉成dict
- except urllib.error.URLError as er:
- if hasattr(er,"code"):
- print("獲取職位信息json對象時發生URLError錯誤,錯誤代碼:")
- print(er.code)
- if hasattr(er,"reason"):
- print("獲取職位信息json對象時發生URLError錯誤,錯誤原因:")
- print(er.reason)
- return pos_dictdef getInfoList(pos_dict):
- '''
- 功能:將getInfoDict()返回的數據字典轉換為數據列表
- @pos_dict:職位信息數據字典
- '''
- pos_list = [] #職位信息列表
- jcontent = pos_dict["content"]["positionResult"]["result"]
- for i in jcontent:
- one_info = [] #一個職位的相關信息
- one_info.append(i["companyFullName"])
- one_info.append(i['companySize'])
- one_info.append(i['positionName'])
- one_info.append(i['education'])
- one_info.append(i['financeStage'])
- one_info.append(i['salary'])
- one_info.append(i['city'])
- one_info.append(i['district'])
- one_info.append(i['positionAdvantage'])
- one_info.append(i['workYear'])
- pos_list.append(one_info)
- return pos_listdef getPosInfo(pos_words,city_words,proxy_addr):
- '''
- 功能:基于函數getInfoDict()與getInfoList(),循環遍歷每一頁獲取最終所有職位信息列表
- @pos_words:職位關鍵詞(多個)
- @city_words:限制城市關鍵詞(多個)
- @proxy_addr:使用的代理IP池(多個)
- '''
- posInfo_result = []
- title = ['公司全名', '公司規模', '職位名稱', '教育程度', '融資情況', "薪資水平", "城市", "區域", "優勢", "工作經驗"]
- posInfo_result.append(title)
- for i in range(0,len(city_words)):
- #i = 0
- key_city = urllib.request.quote(city_words[i])
- #篩選關鍵詞設置:gj=應屆畢業生&xl=大專&jd=成長型&hy=移動互聯網&px=new&city=廣州
- url = "https://www.lagou.com/jobs/positionAjax.json?city="+key_city+"&needAddtionalResult=false"
- for j in range(0,len(pos_words)):
- #j = 0
- page=1
- while page<10: #每個關鍵詞搜索拉鉤顯示30頁,在此只爬取10頁
- pos_wordspos_words_one = pos_words[j]
- #k = 1
- proxy_addrproxy_addr_one = proxy_addr[page]
- #page += 1
- time.sleep(3)
- pos_info = getInfoDict(url,page,pos_words_one,proxy_addr_one) #獲取單頁信息列表
- pos_infoList = getInfoList(pos_info)
- posInfo_result += pos_infoList #累加所有頁面信息
- page += 1
- return posInfo_result#----------------------------------------------------------#######(3)存儲數據###def wXlsConcent(export_path,posInfo_result):
- '''
- 功能:將最終結果寫入本地excel文件中
- @export_path:導出路徑
- @posInfo_result:爬取的數據列表
- '''
- # 打開最終寫入的文件
- wb1 = xlsxwriter.Workbook(export_path)
- # 創建一個sheet工作對象
- ws = wb1.add_worksheet()
- try:
- for i in range(0,len(posInfo_result)):
- for j in range(0,len(posInfo_result[i])):
- data = posInfo_result[i][j]
- ws.write(i,j,data)
- wb1.close()
- except Exception as er:
- print('寫入“'+export_path+'”文件時出現錯誤:')
- print(er)#----------------------------------------------------------#######(4)定義main()函數###def main():
- '''
- 功能:主函數,調用相關函數,最終輸出路徑(F:/spider_ret)下的positionInfo.xls文件
- '''
- #---(1)獲取代理IP池
- proxies = getProxies() #獲取原始代理IP
- proxy_addr = mulTestProxies(proxies) #多線程測試原始代理IP
- #---(2)爬取數據
- search_key = ["數據挖掘"] #設置職位關鍵詞(可以設置多個)
- city_word = ["廣州"] #設置搜索地區(可以設置多個)
- posInfo_result = getPosInfo(search_key,city_word,proxy_addr) #爬取職位信息
- #---(3)存儲數據
- export_path = "F:/spider_ret/positionInfo.xls" #設置導出路徑
- wXlsConcent(export_path,posInfo_result) #寫入到excel中 if __name__ == "__main__":
- main()
接下篇文章《一名合格的數據分析師分享Python網絡爬蟲二三事(Scrapy自動爬蟲)》
【本文是51CTO專欄機構“豈安科技”的原創文章,轉載請通過微信公眾號(bigsec)聯系原作者】