要獲得一個網站所有的網頁URL,思路很簡單,就是一遍遍分析新得到的網頁中有哪些URL,然后不斷重復的。
下面以抓取CSDN為例:
首先是一些輔助用的函數:
1 def getResponse(url):# 使用requests獲取Response 2 headers = { 3 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36' 4 } 5 response = requests.get(url=url, headers=headers) 6 return response 7 8 def getHTMLBySelenium(url):# 使用selenium獲取頁面的page_text 9 try: 10 chrome_options =Options() 11 chrome_options.add_argument('--headless') 12 browser = webdriver.Chrome(executable_path='C:/Users/Administrator/Desktop/chromedriver_win32/chromedriver.exe', options=chrome_options) 13 browser.get(url) 14 time.sleep(2) 15 page_text = browser.page_source 16 browser.quit() 17 return page_text 18 except Exception as e: 19 return '' 20 21 def getBlog(url):# 獲取頁面內容 22 try: 23 page_text = getHTMLBySelenium(url) 24 tree = etree.HTML(page_text) 25 allText = tree.xpath('//body//text()') 26 text = '\n'.join(allText) 27 title = url.replace('/', '_') 28 title = title.replace('.', '_') 29 title = title.replace(':', '_') 30 with open('全站/' + title + '.txt', 'w', encoding='utf-8') as fp: 31 fp.write(text) 32 except Exception as e: 33 return
提取一個頁面中包含的所有其他頁面的URL,具體網站具體分析,這里是CSDN的獲取方式:
def getLinks(url): try: page_text = getHTMLBySelenium(url) tree = etree.HTML(page_text) all_href = tree.xpath('//a') links = [] for href in all_href: link = href.xpath('./@href') if len(link) == 0: continue link = link[0] if link.startswith('https://blog.csdn.net'): links.append(link) return links except Exception as e: return []
下面就是遞歸獲取頁面URL的過程,先看一段簡單的代碼:
urls = set()# 存儲已經被操作過的URL temp1 = set()# 存儲正在被操作的URL temp2 = set()# 存儲新獲取的URL temp1.add('url')# 程序最開始的分析的頁面,可以是網站首頁URL while temp1:# temp1不為空則程序一直運行 for url in temp1: if url in urls:# url在urls 代表這條url已經被處理 continue doSomeThing(url)# 處理url for link in getLinks(url):# 分析url表示的頁面中有哪些其他的URL if link in urls: continue if link in temp2: continue temp2.add(link) # temp1中url處理完畢 # 將temp2內容賦給temp1,並清空temp2 temp1 = temp2.copy() temp2.clear()
從上述代碼可以看到整個程序的運行邏輯,但在具體使用時有一些需要注意的問題:
首先是我們用什么保存獲取的鏈接,我最開始使用的是 set 並將urls,temp1,temp2分別用一個文本文件做備
份,因為我不知道程序會在那個節點出問題,用文本保存后可以避免程序出問題后要重頭開始運行代碼,這也
是上面的輔助函數我都用 try ... except... 的原因。
按照上述思路我完成了第一版的代碼,set + 文本文件,然后程序在周末跑了兩天之后,我周一發現程序把電
腦內存跑滿了(win10 + 16G內存)電腦卡死了,然后強制關機重啟之后我看了一下存儲URL的文件,程序最
外層循環大概運行到第四次,temp2中有幾十萬條URL。
既然內存不夠,接下來我就想將URL存儲到數據庫中,然后我選擇用MySQL代替set存儲URL,仍然用文本做
備份。
下面是這一版本的代碼,如果運行兩天之后程序沒出現內存的問題,這個隨筆就不會再更新:
# ---- 用pymysql 操作數據庫 def get_connection(): conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password) return conn #打開數據庫連接 conn = get_connection()
cnt = 1
loop = 2
cursor = conn.cursor()
cursor1 = conn.cursor()
cursor2 = conn.cursor()
while True: print(f'Loop {loop}') loop += 1 # 遍歷temp1 cursor1.execute("select * from csdn_temp1") while True: temp1Res = cursor1.fetchone() # temp1 遍歷完成 if temp1Res is None: #表示已經取完結果集 break print (temp1Res) url = temp1Res[0] url = re.sub('[\u4e00-\u9fa5]', '', url) cursor.execute("select * from csdn_urls where url = %s", [url]) urlsRes = cursor.fetchone() # 已經抓過這一條鏈接 continue if urlsRes is not None: continue #if cnt % 100 == 0: #print(url) cnt += 1 sql = "insert ignore into csdn_urls values(%s)" cursor.execute(sql,(url)) conn.commit() with open('urls.txt', 'a', encoding='utf-8') as fp: fp.write(url) fp.write('\n') getBlog(url) links = getLinks(url) #toTemp2Urls = [] for link in links: # 已經抓過這一條鏈接 或者 temp2 已經有了這一鏈接 continue cursor.execute("select * from csdn_urls where url = %s", [link]) urlsRes = cursor.fetchone() if urlsRes is not None: continue cursor2.execute("select * from csdn_temp2 where url = %s", [link]) temp2Res = cursor2.fetchone() if temp2Res is not None: continue #toTemp2Urls.append(link) sql = "insert ignore into csdn_temp2 values(%s)" link = re.sub('[\u4e00-\u9fa5]', '', link) cursor2.execute(sql,(link)) conn.commit() with open('temp2.txt', 'a', encoding='utf-8') as fp: fp.write(link) fp.write('\n') #sql="insert ignore into csdn_temp2 values(%s)" #cursor2.executemany(sql,toTemp2Urls) conn.commit() #toTemp2Urls = [] conn.commit() cursor.execute("rename table csdn_temp1 to csdn_temp") conn.commit() cursor.execute("rename table csdn_temp2 to csdn_temp1") conn.commit() cursor.execute("rename table csdn_temp to csdn_temp2") conn.commit() # 刪除temp2數據 cursor.execute("delete from csdn_temp2") conn.commit() os.rename('temp1.txt', 'temp3.txt') os.rename('temp2.txt', 'temp1.txt') os.rename('temp3.txt', 'temp2.txt') with open('temp2.txt', 'w', encoding='utf-8') as fp: fp.write('')
在寫上述代碼時,我遇到了一個問題,表改名后沒有及時commit,使得我之前第一版抓的幾十萬條URL清
空了,而且備份用的文本文件也清空了。修改完之后得到了上述代碼。
整個代碼的調試過程以及寫代碼時的思路可以在我的GitHub上看jupyter文件。