如何以KB编号方式以编程方式获取Microsoft知识库文章的标题?

时间:2016-06-18 02:04:39

标签: c# windows-update

我正在尝试开发一个C#程序,该程序将获取可用的Windows更新列表,并查找知识库文章以检索每个更新的标题。 (否则,它们看起来都很神秘"更新Windows Server(KBxxxxx)")

我尝试检索每篇知识库文章的HTML,但标题不存在于HTML中(我猜测他们使用角度来构建页面)

以下是一个例子:https://support.microsoft.com/en-us/kb/3102429 当我查看源

时,浏览器中显示的文章标题不会出现在HTML中的任何位置

有没有好办法呢?

4 个答案:

答案 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

该页面上的数据是JSON: enter image description here

如果您使用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转移:

enter image description here

答案 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"的第一个孩子是标题,但这可能会改变......(原样: )

enter image description here

答案 3 :(得分:0)

正如Aybe的回答评论中canon所指定的那样,一旦页面加载,KB页面就会通过脚本加载源代码,因此您无法以编程方式轻松获取此内容。

但是,您可以直接使用API​​链接,例如 https://support.microsoft.com/api/content/kb/3102429