Azure函数中C#实现网页登录后调用JS导出Excel的方案咨询
问题描述
我现有一段C#代码,通过自定义WebClient类实现了程序化登录供应商网站并抓取图片存入数据库,运行正常。现在需要获取页面导出按钮(导出为Excel)对应的最新数据,通过F12和Fiddler分析发现该按钮调用一段JavaScript函数。我对Web端知识有限,有两个疑问:
- 是否需要改用WebBrowser?
- 若改用(或继续用现有方式),如何在Azure函数Http触发器中持久化登录Cookie?
该代码将用于Logic Apps流程。
自定义WebClient类代码
public class WebClientEx : WebClient { public CookieContainer CookieContainer { get; private set; } public WebClientEx() { CookieContainer = new CookieContainer(); } protected override WebRequest GetWebRequest(Uri address) { var request = base.GetWebRequest(address); if (request is HttpWebRequest) { (request as HttpWebRequest).CookieContainer = CookieContainer; } return request; } } static string GetSourceForMyShowsPage(string JobId) { var sqlinsert = new SQLInsert(); using (var client = new WebClientEx()) { var values = new NameValueCollection { { "UserName", "sss" }, { "Password", "sss" }, }; // Authenticate client.UploadValues("url/Login", values); //get the image url that we need to get the image in bytes - we might need to change this to a byte method? string responseMessage = client.DownloadString("myurl" + JobId); string imageurl = ""; byte[] imageBytes; //search for images in string HtmlDocument document = new HtmlDocument(); document.LoadHtml(responseMessage); document.DocumentNode.Descendants("img") .Where(e => { string src = e.GetAttributeValue("src", null) ?? ""; return !string.IsNullOrEmpty(src) && src.StartsWith(@"/Photo"); }) .ToList() .ForEach(x => { imageurl = x.GetAttributeValue("src", null); imageurl = imageurl.Substring(0, imageurl.IndexOf("width") - 1); imageurl = imageurl.Replace("amp;", ""); imageurl = imageurl.Replace("&", ""); imageurl = "url/" + imageurl; imageBytes = client.DownloadData(imageurl); string base64image = Convert.ToBase64String(imageBytes); //upload data to sql table sqlinsert.executeInsert(JobId, base64image); }); // Download desired page return client.DownloadString("url" + JobId); } }
需模拟的JavaScript导出函数
function onExportClicked() { var data = ST.Util.serialiseForm($('#shared-tag-search-form')); var url = '/Reports/SharedTag'; var form = $('<form method="POST" action="' + url + '">'); data.push({ name: 'type', value: 'ExportToXls' }); $.each(data, function (k, v) { form.append($('<input type="hidden" name="' + v.name + '" value="' + v.value + '">')); }); $('body').append(form); form.submit(); }
解决方案
是否需要改用WebBrowser?
不需要。WebBrowser是依赖UI环境的页面渲染控件,在Azure函数这类无UI的服务器运行环境中无法正常工作,而且完全没必要——我们可以直接模拟JavaScript函数发起的HTTP请求:
- 拆解JS逻辑:该函数本质是收集
#shared-tag-search-form表单的所有字段,添加type=ExportToXls参数,然后以POST方式提交到/Reports/SharedTag地址。 - 直接用现有
WebClientEx构造相同的POST请求即可,无需执行JavaScript。
如何在Azure函数Http触发器中持久化登录Cookie?
Azure函数是无状态的,每次调用都会初始化新实例,因此需要将登录后的CookieContainer序列化存储到外部介质,下次调用时反序列化复用:
- 序列化CookieContainer:可以用二进制序列化或自定义格式提取Cookie(注意保留Cookie的Domain、Path等属性)。
- 存储介质选择:推荐使用Azure Blob存储、Azure Redis缓存或SQL数据库,根据成本和性能需求决定。
- 复用Cookie:下次请求前从存储介质读取并反序列化CookieContainer,赋值给
WebClientEx的对应属性,避免重复登录。
示例:添加导出Excel的方法
在现有逻辑中新增导出逻辑,直接模拟POST请求:
static byte[] ExportExcel(string JobId, WebClientEx client) { // 先获取页面,提取#shared-tag-search-form的表单字段(若表单为动态生成) string pageHtml = client.DownloadString("url" + JobId); HtmlDocument document = new HtmlDocument(); document.LoadHtml(pageHtml); // 提取表单字段(模拟ST.Util.serialiseForm的逻辑) var formData = new NameValueCollection(); document.DocumentNode.Descendants("input") .Where(e => e.HasAttribute("name") && !string.IsNullOrEmpty(e.GetAttributeValue("name", ""))) .ToList() .ForEach(input => { string name = input.GetAttributeValue("name", ""); string value = input.GetAttributeValue("value", ""); formData.Add(name, value); }); // 添加导出类型参数 formData.Add("type", "ExportToXls"); // 发起POST请求,获取Excel字节流 return client.UploadValues("url/Reports/SharedTag", "POST", formData); }
示例:Cookie持久化到Azure Blob
// 序列化CookieContainer为字节数组 static byte[] SerializeCookieContainer(CookieContainer container) { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, container); return ms.ToArray(); } } // 反序列化字节数组为CookieContainer static CookieContainer DeserializeCookieContainer(byte[] data) { using (var ms = new MemoryStream(data)) { var formatter = new BinaryFormatter(); return formatter.Deserialize(ms) as CookieContainer; } } // 将Cookie存储到Azure Blob async static Task SaveCookiesToBlob(byte[] cookieData, string blobName) { var blobServiceClient = new BlobServiceClient("your-azure-storage-connection-string"); var containerClient = blobServiceClient.GetBlobContainerClient("cookies-container"); await containerClient.CreateIfNotExistsAsync(); var blobClient = containerClient.GetBlobClient(blobName); await blobClient.UploadAsync(new MemoryStream(cookieData), overwrite: true); } // 从Azure Blob读取Cookie async static Task<CookieContainer> LoadCookiesFromBlob(string blobName) { var blobServiceClient = new BlobServiceClient("your-azure-storage-connection-string"); var containerClient = blobServiceClient.GetBlobContainerClient("cookies-container"); var blobClient = containerClient.GetBlobClient(blobName); if (!await blobClient.ExistsAsync()) return null; using (var ms = new MemoryStream()) { await blobClient.DownloadToAsync(ms); ms.Position = 0; return DeserializeCookieContainer(ms.ToArray()); } }
内容的提问来源于stack exchange,提问作者Sunette
相关产品推荐
相关产品推荐

