我正在尝试开发一个C#程序,该程序将获取可用的Windows更新列表,并查找知识库文章以检索每个更新的标题。 (否则,它们看起来都很神秘"更新Windows Server(KBxxxxx)")
我尝试检索每篇知识库文章的HTML,但标题不存在于HTML中(我猜测他们使用角度来构建页面)
以下是一个例子:https://support.microsoft.com/en-us/kb/3102429 当我查看源
时,浏览器中显示的文章标题不会出现在HTML中的任何位置有没有好办法呢?
答案 0 :(得分:3)
对于2017年8月之后发布的修补程序,新API链接似乎为https://support.microsoft.com/app/content/api/content/help/en-us/4034733。
对于2017年2月之后发布的修补程序,新的API链接似乎是https://support.microsoft.com/api/content/help/3115489。
如果您使用Python加载该JSON数据,例如,您可以在"详细信息"下找到标题和其他有用信息。特别是,
d["details"]["id"] == u'3115489'
d["details"]["title"] == u'February 7, 2017, update for Office 2013 (KB3115489)'
d["details"]["publishedOn"] == u'2017-02-07T17:05:19.000368Z'
仅供参考,在运行开发者工具的Chrome中加载网址https://support.microsoft.com/kb/3115489时,网络活动会显示来自api / content / help的XHR转移:
答案 1 :(得分:1)
我发现他们现在正在将一些预取脚本放入包含一些有用json的初始有效负载中。 (实际上:这是b.mcewan在currently accepted answer中提到的json。)
由于我已经准备好使用所有这些东西。...这里是一些代码的链接,这些代码将收集您机器上已安装的修补程序并提供一些详细信息,包括KB标题。
代码将在LINQPad中运行 http://share.linqpad.net/l6tdxc.linq
如果您不使用LP,请参见以下例程。 ParseTitle利用一些自动生成的类对json反序列化。您将需要删除.Dump()扩展方法调用和Hyperlinq类引用,并以其他方式显示数据。 (编辑:不仅仅是ArticleInfo类公开的知识库文章标题...。例如有关此修补程序的详细信息,如何获取和安装该修补程序的详细信息。)
void Main()
{
const string query = "SELECT HotFixID, InstalledOn, InstalledBy, Description, Caption, * FROM Win32_QuickFixEngineering";
var result =
(from ManagementObject quickfix in new ManagementObjectSearcher(query).Get() //.AsParallel()
orderby Convert.ToDateTime(quickfix["InstalledOn"]) descending
let web = new WebClient()
let input = quickfix["Caption"].ToString()
let id = input.Substring(35, input.Length - 35)
let url = $"{input.Replace("microsoft.com/?kbid=", "microsoft.com/en-us/help/")}/kb{id}"
let html = web.DownloadString(url)
where string.IsNullOrEmpty( html ).Equals(false)
let kbInfo = ParseInfo( url, html )
where kbInfo != null
let pub = kbInfo.Details.PublishedOn
let title = kbInfo.Details.Title
let desc = Util.OnDemand( "More....", () =>
Util.RawHtml(string.Join(Environment.NewLine,
kbInfo.Details.Body
.Select(i => $"<span class=typeglyphx>{i.Title}</span>{i.Content.Single()}")))
)
select
new
{
HotFixID = Util.RawHtml($"<span class=typeglyphx>{quickfix["HotFixID"].ToString()}</span>"),
Published = pub.Date,
InstalledOn = quickfix["InstalledOn"].ToString(),
InstallDelay = $"{Convert.ToInt16((Convert.ToDateTime(quickfix["InstalledOn"].ToString()).Date - pub.Date).TotalDays)} days",
InstalledBy = quickfix["InstalledBy"].ToString(),
Description = new Hyperlinq(quickfix["Description"].ToString()),
Title = Util.RawHtml($"<span class=typeglyphx>{title}</span>") ?? $"{url} [Could not obtain KB title]",
Body = desc,
Link = new Hyperlinq(url),
}
).Dump(1);
}
#nullable enable
string? ParseTitle ( string html )
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
var meta = doc.DocumentNode
.SelectNodes("//script");
var searchToken = "microsoft.support.prefetchedArticle = (function() ";
var nuggets = meta
.Where(i => i.OuterHtml.Contains(searchToken))
.Select(i => i.OuterHtml)
.Single();
var start = nuggets.IndexOf(":") + 1;
var length = nuggets.Length - start - 28;
var json = nuggets.Substring(start, length);
string? ret = null;
try
{
var articleInfo = MSKBPreFetched.ArticleInfo.FromJson(json);
ret = articleInfo.Details.Title;
}
catch{ json.DumpTrace("could not deserialize the json for this article"); // LP only}
return ret;
}
#nullable disable
// <auto-generated />
// json2csharp
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using MSKBPreFetched;
//
// var articleInfo = ArticleInfo.FromJson(jsonString);
namespace MSKBPreFetched
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class ArticleInfo
{
[JsonProperty("sideNav")]
//[JsonConverter(typeof(ParseStringConverter))]
public string SideNav { get; set; }
[JsonProperty("details")]
public Details Details { get; set; }
[JsonProperty("_ts")]
public long Ts { get; set; }
}
public partial class Details
{
[JsonProperty("subType")]
public string SubType { get; set; }
[JsonProperty("heading")]
public string Heading { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("body")]
public List<Body> Body { get; set; }
[JsonProperty("urltitle")]
public string Urltitle { get; set; }
[JsonProperty("keywords")]
public List<string> Keywords { get; set; }
[JsonProperty("keywordsLower")]
public List<string> KeywordsLower { get; set; }
[JsonProperty("os")]
public List<object> Os { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("id")]
[JsonConverter(typeof(ParseStringConverter))]
public long Id { get; set; }
[JsonProperty("locale")]
public string Locale { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("titleLower")]
public string TitleLower { get; set; }
[JsonProperty("published")]
public bool Published { get; set; }
[JsonProperty("createdOn")]
public DateTimeOffset CreatedOn { get; set; }
[JsonProperty("publishedOn")]
public DateTimeOffset PublishedOn { get; set; }
[JsonProperty("version")]
public long Version { get; set; }
[JsonProperty("eolProject")]
public string EolProject { get; set; }
[JsonProperty("supportAreaPaths")]
public List<Guid> SupportAreaPaths { get; set; }
[JsonProperty("supportAreaPathNodes")]
public List<PrimarySupportAreaPath> SupportAreaPathNodes { get; set; }
[JsonProperty("disableVAPopup")]
public bool DisableVaPopup { get; set; }
[JsonProperty("primarySupportAreaPath")]
public List<PrimarySupportAreaPath> PrimarySupportAreaPath { get; set; }
[JsonProperty("isContentLocaleFallback")]
public bool IsContentLocaleFallback { get; set; }
[JsonProperty("contentLocale")]
public string ContentLocale { get; set; }
}
public partial class Body
{
[JsonProperty("meta")]
public Meta Meta { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("content")]
public List<string> Content { get; set; }
}
public partial class Meta
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("products")]
public List<object> Products { get; set; }
[JsonProperty("supportAreaPaths")]
public List<object> SupportAreaPaths { get; set; }
[JsonProperty("isInternalContent")]
public bool IsInternalContent { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
}
public partial class PrimarySupportAreaPath
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("parent", NullValueHandling = NullValueHandling.Ignore)]
public Guid? Parent { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("tree")]
public List<object> Tree { get; set; }
}
public partial class ArticleInfo
{
public static ArticleInfo FromJson(string json) => JsonConvert.DeserializeObject<ArticleInfo>(json, MSKBPreFetched.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this ArticleInfo self) => JsonConvert.SerializeObject(self, MSKBPreFetched.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class ParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
long l;
if (Int64.TryParse(value, out l))
{
return l;
}
throw new Exception("Cannot unmarshal type long");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (long)untypedValue;
serializer.Serialize(writer, value.ToString());
return;
}
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
}
}
答案 2 :(得分:0)
如果以某种方式可以从Windows Update中获取KB编号,则可以通过以下URL访问该文章:
https://support.microsoft.com/en-us/kb/YOUR_KB_NUMBER
id="mt5"
似乎是标题。
修改强>
我的不好,id
确实发生了变化,<section>
class="section kb-article spacer-84-top"
的第一个孩子是标题,但这可能会改变......(原样: )
答案 3 :(得分:0)
正如Aybe的回答评论中canon所指定的那样,一旦页面加载,KB页面就会通过脚本加载源代码,因此您无法以编程方式轻松获取此内容。
但是,您可以直接使用API链接,例如 https://support.microsoft.com/api/content/kb/3102429