Python + selenium 爬取网页信息
为了收集一些有价值的信息而最近就开始尝试用Python进行简单的爬虫开发。网上有很多方法可供选择:如Request、BeautifulSoup、Selenium以及Scrapy等。由于要爬取的网站中有些网站的数据是动态加载的,在此选择使用Selenium并搭配ChromeDriver来获取页面数据。技术还不够熟练,在此记录下学习过程。
目前的比较分析表明
网页抓取(web scraping)
目的:通过智能整合各招投标网站实时更新的相关项目详细信息,并提取并整理出每个项目的标题名称、发布来源链接地址以及发布日期;通过智能筛选功能定位到目标文章,并获取其正文中的核心内容数据。
访问目标网站的一般步骤通常是首先访问目标网站并导航至内容区域。随后系统会识别所需内容的位置通常依赖于标签或结构信息。例如,在新闻聚合器中提取新闻标题和链接通常涉及通过特定标签定位文章标题并捕获相关链接地址随后将提取的内容进行保存以供后续处理
from requests_html import HTMLSession
session = HTMLSession()
# 获取网页
r = session.get("https://news.cnblogs.com/n/recommend")
# 通过CSS找到新闻标签
news = r.html.find('h2.news_entry > a')
for new in news:
print(new.text) # 获得新闻标题
print(new.absolute_links) # 获得新闻链接
然而,在某些网站中,在获取网页后并未成功找到所需的数据时,则只能提取到该网页的HTML文档信息,并无法解析并执行相应的CSS与JavaScript代码。Selenium模块的本质是通过控制浏览器的行为来模仿浏览器的操作流程,在这一过程中能够捕获浏览时会跳转到其他页面时所显示的内容,并且支持多种不同的 browsers 的操作模式;
0 准备及说明:
0.1 网站URL地址,请查找元素定位(XPATH的用法请参考这篇相关文章),由于需要爬取多个网站的配置信息,请将这些配置保存到Excel文件中。

0.2 思路:
- 遍历各个网站收集每个网站的所有文章标题、详情页链接以及发布日期(仅限过去七天)。
- 对相关文章进行初步筛选。(这一步是为了节省时间而添加的内容)
- 即使初步筛选后的数据量不高(无需多线程),我们仍然可以选择进一步优化算法并将其记录下来。
- 针对每个链接进行详细的数据提取。
1. 安装selenium+chromdriver.exe
2. 代码
2.1 对于单个网站示例
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from datetime import *
import time
import os
import re
result=[]
path="D:/" # chromdriver.exe 放的路径
url=""
title_loc=""#title的位置的xpath
link_loc=""#link的位置的xpath
driver = webdriver.Chrome(path)
driver.get(url)
driver.maximize_window()
total_pages = driver.find_elements_by_class_name('pageItem')[-1].get_attribute('page-data')#有多少页
for i in range(1,total_pages+1):
page_result=[]
if page != 1:
driver.find_element(By.XPATH,loc_list[3]).click()
sleep(3)
title_list = driver.find_elements(By.XPATH,title_loc)
link_list = driver.find_elements(By.XPATH,link_loc)
for i in range(len(title_list)):
title = title_list[i]
url=link_list[i].get_attribute("href")
page_result.append([title, url])
result.append(page_result)
driver.close()
2.2 多个网站例子
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from datetime import *
import time
import os
import re
today= datetime.now().strftime("%Y-%m-%d")
sevendayago=str(date.today() - timedelta(days=7))
path="" # chromdriver.exe 放的路径
1.遍历所有网站,获得发布时间在7日内的标题,url,发布日期

* 对比日期
def compareTime(date, last_time):
format_pattern = '%Y-%m-%d'
difference = (datetime.datetime.strptime(date, format_pattern) - datetime.datetime.strptime(last_time,format_pattern))
if difference.days < 0:
return False
else:
return True
def get_url(driver,loc_list):
tag=0 # 如果文章发布时间离现在太久就标记1,跳出循环,不往下获取
page_result = []
title_loc,link_loc,date_loc=loc_list[4],loc_list[5],loc_list[6]
title_list = driver.find_elements(By.XPATH,title_loc)
link_list = driver.find_elements(By.XPATH,link_loc)
date_list = driver.find_elements(By.XPATH, date_loc)
for i in range(len(title_list)):
date = date_list[i]
if compareTime(date ,sevendayago):# 比较时间,只要最近7天的
title = title_list[i]
url=link_list[i].get_attribute("href")
page_result.append([title, url])
else:
tag+=1
break
return page_result,tag
def f(driver,loc_list):
#关于翻页
# - 可以定位总页数,来判断是否翻页,
# 比如: total_str = driver.find_elements_by_class_name('pageItem')[-1].get_attribute('page-data')
# 但是多个网站xpath不一,
# - 这里我用的是判断下一页之后的url是否有改变
# 但是有些网站的翻页或者点击分类选项,url都没有变化,那个需要另外考虑了。
lastPageUrl = ""
currentPageUrl=driver.current_url
result = []
next_loc=loc_list[3]
while currentPageUrl!=lastPageUrl :
t, tag= get_url(driver,loc_list)#t是2维数组
for i in t:
result.append(i)
if tag!=0:#
break
driver.find_element(By.XPATH,next_loc).click()
sleep(1)
lastPageUrl =currentPageUrl
currentPageUrl= driver.current_url
driver.close()
return result
def main():
Source_list=getSource("Webs.xlsx")# 从配置表获取网站url,xpath等,略
all_result=[]
for loc_list in Source_list:# loc_list 是[序号,来源网站,网址,下一页标志位,标题标志位,链接位,发布时间]
n,source,url=loc_list[0],loc_list[1],loc_list[2]
#print("n=", n, "网站:", source)
driver = webdriver.Chrome(path)
driver.set_page_load_timeout(10)
driver.set_script_timeout(10)
try:
driver.get(url)
driver.maximize_window()
except Exception as e:
print("抛出异常:",repr(e))
driver.maximize_window()
try:# 有的网站页面元素已经出来,但是一直显示加载,超时后,看看能否照样获取元素
driver.find_element(By.XPATH,loc_list[3])
except Exception as e:
driver.close()
continue
sleep(1)
try:
result=f(driver,loc_list,mode)# 获取标题,url
except Exception as e:
driver.close()
continue
saveOutput(all_result)# 保存url等在excel里,略
# 2. 假如用多线程,把所有的url等份,便于后续作多线程爬取
import math
def slice_url(slice_num):
# 读取所有的url
urls=getSource(excel_path)#读写excel的函数会在后面记录
urls_num = len(urls)
step = math.ceil(urls_num / slice_num)
# 写url
for i in range(slice_num):
with open('保存的路径' + str(i+1) + '.txt', 'w', encoding='utf-8') as f:
for j in range(step*i, step*(i+1)):
try:
f.write(urls[j])
except:
break
if __name__=='__main__':
main()
slice_url(20)
3.获取详情页数据 , 比如项目编号,预算金额,采购人
由于各网站的具体页面结构存在差异,在某些情况下可能仅存在于静态文本中,并且无法通过简单的正则匹配直接获取;对于无法从动态加载的内容中获取的数据,则通常采用定位技术来解决;例如,在某些页面上可能直接提供该信息(如"xx项目征求意见公告"),因此需要根据具体情况进行相应调整;例如供参考(注:这只是其中一种实现方式)。



def read_url(file_path): # 读取分好的的urls
with open(file_path, 'r') as f:
urls = f.readlines()
return urls
def init_driver(url, index):
global threads
threads['Thread_' + str(index)] += 1
print('Thread_' + str(index) + '\t' + str(threads['Thread_' + str(index)]))
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("headless") # 设置无头浏览器,就是不打开浏览器
driver_path = path
driver = webdriver.Chrome(options=chrome_options, executable_path=driver_path)
driver.set_page_load_timeout(10)
driver.set_script_timeout(10)
try:
driver.get(url)
except:
pass
return driver
# 用正则的话
def getinfo(text):
price=""
item_number = "" # 项目编号
company = "" # 采购人信息
location = ""# 地址
price_loc=re.search(r'(预算金额:|项目预算:).*',text)
if price_loc:
price=price_loc.group(0).split(":")[1]
item_number_loc=re.search(r'(项目编号:|招标编号:).*',text)#
if item_number_loc:
item_number=item_number_loc.group(0).split(":")[1]
company_loc=re.findall(r'采购人信息(?:.|\n)(.*)(?:.|\n)(.*)|招标人(:.*)(?:.|\n)(.*)|招标人名称(:.*)(?:.|\n)(.*)|采购人联系方式(?:.|\n)(.*)(?:.|\n)(.*)',text) #(?:.|\n)换行
if company_loc:
t=company_loc[0]
for n in range(10):
if t[n]=='':
continue
if n%2==0:#name
company=t[n].split(":")[1]
else:
location=t[n].split(":")[1]
return [item_number,price,company,location]
def get_data(index):
urls = read_url('之前存的txt_path')
info_list=[]
for url in urls:
driver = init_driver(url, index)
get_info(driver, index)
# 正则获取
text = driver.find_element(By.XPATH, "//body").get_attribute("innerText")
info_list.append(get_info(text))
#
driver.close()
saveText(info_list)
if __name__ == '__main__':
global threads
threads = {} # 上文slice_url中共切分成20份,每次运行使用10个线程来爬取,这里分2次运行,(1-11,11-21)
for index in range(1, 11):
threads['Thread_' + str(index)] = 0
thread = threading.Thread(target=get_data, args=(index,))
thread.start()
4 . 网页截图 PhantomJS(),需下载phantomjs.exe,PhantomJS()是无界面的
def addPng():
name_col=[]
source, title,url= xpath略
name=title+".png"
name_path=save_path+"/"+name
brower = webdriver.PhantomJS() # 使用webdirver.PhantomJS()方法新建一个phantomjs的对象,这里会使用到phantomjs.exe,环境变量path中找不到phantomjs.exe,则会报错
brower.set_page_load_timeout(10)
brower.set_script_timeout(10)
try:
brower.get(url) # 使用get()方法,打开指定页面。注意这里是phantomjs是无界面的,所以不会有任何页面显示
brower.maximize_window() # 设置phantomjs浏览器全屏显示
brower.save_screenshot(name_path) # 使用save_screenshot将浏览器正文部分截图,即使正文本分无法一页显示完全,save_screenshot也可以完全截图
brower.close()
except:
print("截图失败")
brower.close()
遇到的问题:
Python用于处理文件时遇到非法多字节序列的问题;text变量赋值爬取的文本内容较长。
with open(full_path, "w") as file:
file.write(text)
在使用时经常会遇到这样的问题:在某些情况下,“GBK”编码会导致balabala出现非法多字节序列。通过将编码设置为'utf-8'即可解决问题。
with open(full_path, "w",encoding='utf-8') as file:
file.write(text)
偶尔启动Selenium和Chrome运行某个网站可能会导致长时间保持打开状态;然而,在此过程中网页已成功打开,并且所有相关元素均已定位成功。
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
#get直接返回,不再等待界面加载完成
desired_capabilities = DesiredCapabilities.CHROME
desired_capabilities["pageLoadStrategy"] = "none"
driver = webdriver.Chrome(executable_path='chromedriver.exe')
配置一个参数用于调整页面加载方式,默认情况下会等到页面完全加载完毕;如果将该参数设置为none时,则不会进行等待操作。这将导致一旦GET操作完成就会立即停止,并不影响后续的操作流程。
设置无头谷歌浏览器,还需要driver.close()或者driver.quit()吗?
从文本中提取所需信息,并利用regular expressions和parsing library进行操作。如果你遇到空格问题,请确保使用特定的方式进行处理,并确保格式正确。
创建文本,并写入的时候出现以下错误:
with open(f, "w",encoding='utf-8') as file:
OSError: [Errno 22] Invalid argument:balabala(我的.txt名字)
可能的原因是:在打开文件名时包含了某些系统特定的特殊字符(如? * : " < > \ / | )会导致程序报错。在网上查阅资料后发现,在Windows环境下命名文件时不能包含一些特殊的字符(如? * : " < > \ / | ),否则会引发冲突。这些特殊字符在Windows系统中各自具有特定的意义。因此需要将这些特殊字符过滤掉:
#translate方法
intab = "?*/\|.:><"
outtab = " "
trantab = str.maketrans(intab, outtab)
title = '把我看|成?新.闻\的*标题把。'.translate(trantab)
contetn = '就把我看成是新闻的内容把!!!'
with open(title+'.txt', 'a', encoding='utf-8') as f:
f.write(contetn)
