构建一个用于过滤、导出、下载网页链接的工具






3.94/5 (6投票s)
网站链接和链接文件管理器,用于自动管理和过滤链接,以节省大量时间。

引言
如今,随着网站及其服务的不断增长,网络用户在查找和过滤所需链接时,必须逐个检查链接以查找特定单词、字符串或遵循某些规则的结构,以便选择或不选择链接。对于图库网站或具有特定链接的多级网站,这些步骤会耗费用户大量时间。
如果用户可以通过某些规则过滤网站链接,就有可能高精度地提取所需链接。
例如,当我们有一个网页上的以下链接时
1-20x20.jpg
1-100x100.jpg
1.jpg.gif
1.jpg
2-20x20.jpg
2-100x100.jpg
2.gif
2.jpg
.
.
.
1000-20x20.jpg
1000.jpg.gif
1000-100x100.jpg
1000.jpg
如果我们想要的链接只是 1.jpg 、 2.jpg 、… 、 999.jpg 、 1000.jpg ,我们需要通过删除包含字符串 20 或 100 的链接来过滤网页链接,并获得剩余的链接列表。
规则
删除至少包含字符串“20”或“100”或“.gif”的链接,并获得包含字符串“.jpg”(不含引号)的链接。
这可以用斜杠(/)分隔的字符串来解释: 20/100/.gif 作为不需要的字符串列表,链接中包含这些字符串将被排除在最终列表之外; .jpg 作为强制包含 .jpg 的最终链接。这可以作为不需要的字符串列表和期望的字符串列表的高级选项。
网站通过链接和链接文件夹树扩展,创建网站级别。在大多数情况下,网络用户对网站地图的子树感兴趣,并在该部分进行网页浏览。以下设置控制此功能:
- 从起始 URL 开始的最大搜索级别
- 是否停留在同一域
- 最大链接数,以避免在网络复杂性过多的链接中陷入循环,经过数小时的搜索并确保导出找到的链接列表
- 停止搜索功能(按钮),用于停止搜索
搜索结果可以导出到链接文件。管理链接文件中的大量链接也可能非常耗时。在此工具中,添加了以下选项来管理链接文件:
- 通过过滤选项(期望的字符串和不需要的字符串列表)过滤链接文件中的链接。
- 获取顶级文件夹或带有子文件夹的文件夹中的文件名列表,并删除链接文件中重复的链接,仅导出新链接。
背景
网页下载工具和一些网页转换器具有一些类似选项,但我开发此工具是为了将其作为开源软件提供,并在必要时可随时修改。易于使用和过滤,可以根据所需的字符串计数(区分大小写、期望的字符串和不需要的字符串)。
构建此工具的想法一直在我脑海中,之后我通过网络搜索发现 WebClient 的功能,尤其是在 .Net framework 3.5 中,实施已开始。
使用代码
WebManagement 类
为了使用网络资源,我们必须首先构建一个 Web 管理器,它实现客户端操作,例如下载网页源代码、提取链接、下载链接目标、使用代理服务器和连接工作流。
项目主类是 WebManagement,该类的属性和方法被实现为静态的,以方便使用。
属性
我假设最多同时存在一个下载,并使用一个控件来直观地显示下载进度:progressBar。用户窗体上有一个标签,用于显示 WebManagement 状态:lblMessage。用于临时工作的文件夹路径:TempFile。
为了控制搜索过程,使用了布尔型停止变量:isStop。
连接代理的使用由布尔型变量控制:UsingFromProxy。
代理服务器 IP 地址:ProxyServer
代理服务器端口号:ProxyPort
public static ProgressBar progressBar = null;
public static Label lblMessage = null;
public static string TempFile = null;
public static bool isStop = false;
public static bool UsingFromProxy;
public static string ProxyServer;
public static string ProxyPort; 
注意:如果必须通过代理服务器建立连接,则会创建 webClient 的 proxy 属性(一个 WebProxy 实例类),并设置为代理服务器和端口:
if (UsingFromProxy == true)
    webClient.Proxy = new WebProxy(ProxyServer, int.Parse(ProxyPort));
方法
方法 GetFileSource(string url):
下载网页源代码(HTML源代码),使用 webClient.DownloadString 将网页源代码作为字符串下载并导出。
public static string GetFileSource(string url)
{
    try
    {
        if (isStop == true)
            return "";
        WebClient webClient = new WebClient();
        if (UsingFromProxy == true)
            webClient.Proxy = new WebProxy(ProxyServer, int.Parse(ProxyPort));
        return webClient.DownloadString(new Uri(url));
    }
    catch
    {
        return "";
    }
}
方法 DownloadWebFile(string url, string tarPath)
从 web url 下载到 tarPath,并连接到两个事件 DownloadFileCompleted 和 DownloadProgressChangedEventHandler,并使用 WebClient 类的 DownloadFileAsync 方法从 Web 下载。
public static bool DownloadWebFile(string url, string tarPath)
{
    try
    {
        WebClient webClient = new WebClient();
        if (UsingFromProxy == true)
            webClient.Proxy = new WebProxy(ProxyServer, int.Parse(ProxyPort));
        webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
        webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
        webClient.DownloadFileAsync(new Uri(url), tarPath);
        webClient.Dispose();
        webClient = null;
        return true;
    }
    catch
    {
        return false;
    }
}
private static void Completed(object sender, AsyncCompletedEventArgs e)
{
    if (lblMessage != null)
        lblMessage.Text = "Download completed!";
}
private static void ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    if (progressBar != null)
    {
        progressBar.Value = e.ProgressPercentage;
        lblMessage.Text = e.ProgressPercentage.ToString() + " % is completed";
    }
}
方法 List<string> FindLinks(string FileText)
此方法从网页源代码(HTML源代码文本)中提取链接,并导出为 List<string>
HTML 源代码中的链接标签结构
<a href=URL > 显示文本 </a>
我们必须收集源代码中的 URL,为此,使用 Regex(正则表达式)类是最佳选择,并执行以下三个步骤从网页源代码中提取 URL:
- 通过匹配 @"(<a.*?>*?</a>)" 查找链接
- 通过匹配 @"href=""(.*?)""" 从每个链接中提取 URL
- 使用函数删除 href URL 文本中的内部标签
Regex.Replace(value, @"\s*<.*?>\s*", "",RegexOptions.Singleline);
public static List<string> FindLinks(string FileText)
{
    Application.DoEvents();
    List<string> list = new List<string>();
    // 1.
    // Find all matches in file.
    //MatchCollection m1 = Regex.Matches(URL, @"(<a.*?>.*?</a>)",
    //    RegexOptions.Singleline);
    MatchCollection m1 = Regex.Matches(FileText, @"(<a.*?>*?</a>)",
        RegexOptions.IgnoreCase | RegexOptions.Singleline);
    // 2.
    // Loop over each match.
    foreach (Match m in m1)
    {
        string value = m.Groups[1].Value;
        LinkItem i = new LinkItem();
        // 3.
        // Get href attribute.
        //Match m2 = Regex.Match(value, @"href=\""(.*?)\""",
        //RegexOptions.Singleline);
        Match m2 = Regex.Match(value, @"href=""(.*?)""",
        RegexOptions.Singleline | RegexOptions.IgnoreCase);
        if (m2.Success)
        {
            i.Href = m2.Groups[1].Value;
        }
        // 4.
        // Remove inner tags from text.
        //string t = Regex.Replace(value, @"\s*<.*?>\s*", "",
        //RegexOptions.Singleline);
        string t = Regex.Replace(value, @"\s*<.*?>\s*", "",
        RegexOptions.Singleline);
        i.Text = t;
        list.Add(i.Href);
    }
    return list;
}方法
bool AddLinks(DataGridView dg,string url,int level,string file,
  string [] elseStr,string [] withStr,bool lastLevel,ArrayList PreLinks ,bool justCurrentSite)
此方法获取级别 level 的 URL url,以及 HTML 源代码 file,不包含字符串的数组 elseStr,包含字符串的数组 withStr,布尔型 lastLevel,它确定是否是最后一个级别,一个 ArrayList 用于避免重复的链接 URL,布尔型输入 justCurrentSite 用于强制停留在同一域或不。
将链接从 URL 添加到 datagridview 的步骤
- 用 List<string> lst = FindLinks(file); 提取链接
- 从 url(绝对 URL 地址)获取 AbsoluteUri 并添加到 PreLinks
- 对于 lst 中的每个 url
- 如果链接的绝对 URL 长度大于其父链接 URL,并且输入 URL 的主机与新 URL 的主机相等,并且 justCurrentSite 选项为 true(已检查同一域),则这是一个有效的新链接。
- 如果链接的 URL 主机是另一个主机,并且 justCurrentSite 选项为 false(未检查同一域),并且不在 PreLink ArrayList 中,则这是一个有效的“新”链接。
- 如果当前链接是一个有效的“新”链接,则将 URL 的绝对 URL 地址添加到 PreLink ArrayList 并检查以下条件:
- 检查是否不包含任何不需要的字符串列表。
- 如果包含字符串列表不为空,则检查是否至少包含一个来自包含字符串列表的字符串。
如果前两个检查为真,则此新链接信息将被添加到 dataGridView 的行中。
public static bool AddLinks(DataGridView dg, string url, 
        int level, string file, string[] elseStr, string[] withStr, 
        bool lastLevel, ArrayList PreLinks, bool justCurrentSite)
{
    try
    {
        if (isStop == true)
            return false;
        bool ret = false;
        List<string> lst = FindLinks(file);
        Uri ba = new Uri(url);
        PreLinks.Add(ba.AbsoluteUri);
        for (int i = 0; i < lst.Count; i++)
        {
            Application.DoEvents();
            if (isStop == true)
                return false;
            Uri ur = new Uri(ba, lst[i]);
            bool ActiveLink = false;
            if (ur.AbsoluteUri.Length > url.Length && (ur.Host == ba.Host))
                ActiveLink = true;
            if (justCurrentSite == false)
                if ((ur.Host != ba.Host) && PreLinks.BinarySearch(ur.AbsoluteUri) < 0)
                    ActiveLink = true;
            if (ActiveLink == true)
            {
                PreLinks.Add(ur.AbsoluteUri);
                bool flag = true;
                string ft = "p";
                string[] sec = lst[i].Split('/');
                if (sec.Length > 0)
                    if (sec[sec.Length - 1] != null)
                        if (sec[sec.Length - 1].Contains(".") == true)
                        {
                            ft = "";
                        }
                if (ft == "f" || lastLevel == true)
                {
                    for (int k = 0; k < elseStr.Length; k++)
                        if (lst[i].Contains(elseStr[k]) == true && elseStr[k] != "")
                        {
                            flag = false;
                            break;
                        }
                    if (flag == true && withStr.Length > 0)
                    {
                        flag = false;
                        for (int m = 0; m < withStr.Length; m++)
                            if (lst[i].Contains(withStr[m]) == true)
                            {
                                flag = true;
                                break;
                            }
                    }
                }
                if (flag == true)
                {
                    Application.DoEvents();
                    dg.Rows.Add();
                    int j = dg.Rows.Count - 1;
                    dg.Rows[j].Cells[5].Value = ft;
                    if (ft == "p")
                        ret = true;
                    dg.Rows[j].Cells[0].Value = i + 1;
                    dg.Rows[j].Cells["Link"].Value = ur.AbsoluteUri;
                    dg.Rows[j].Cells[4].Value = level;
                }
            }
        }
        return ret;
    }
    catch (Exception exp)
    {
        MessageBox.Show(exp.Message);
        return true;
    }
}
方法
void GetLinks(DataGridView dg, string url, string fileTypes,string [] elseStr,string [] withStr, int level,bool justCurrentSite,long Maxcnt)
此方法从 URL url 开始查找链接到文件类型(用于未来实现),不需要的字符串数组 elseStr,包含字符串数组 withStr,最大级别 level,布尔型输入 justCurrentSite 用于强制停留在同一域或不,MaxCnt 是所有链接计数限制,如果为零则表示无限制。
此方法是获取具有所有条件的网页链接的管理方法。
处理步骤
- 创建 ArrayList PreLinks 以保存不同的链接。
- 获取一个布尔型变量 flag 用于主循环,以确定内部循环操作是否产生了一个新链接(可能未达到最大级别)。
- 按级别计数循环
- 如果 dataGridView 的行列表为空,则从输入 URL 开始,并进行以下函数调用:
- 从行号零循环到 cnt-1
- 如果当前行是文件夹并且不是最终文件链接(检查 DataGridView的LinkisFile列,如果其值为 p),则调用当前行的添加链接。
- 删除当前行,因为其链接已被添加。
flag=AddLinks(dg, url, f + 1, GetFileSource(url), elseStr, 
  withStr, (f == level - 1),PreLinks,justCurrentSite);
否则(如果 DataGridView Rows 列表不为空),将 cnt 设置为 Rows.Count。
public static void GetLinks(DataGridView dg, string url, 
     string fileTypes, string[] elseStr, string[] withStr, int level, bool justCurrentSite, long Maxcnt)
{
    if (isStop == true)
        return;
    Application.DoEvents();
    ArrayList PreLinks = new ArrayList((int)(Maxcnt == 0 ? 1000 : Maxcnt) + 1);
    bool flag = true;
    for (int f = 0; f < level && flag; f++)
    {
        if (isStop == true)
            return;
        flag = false;
        int cnt = dg.Rows.Count;
        if (f == 0 && dg.Rows.Count == 0)
        {
            Application.DoEvents();
            flag = AddLinks(dg, url, f + 1, GetFileSource(url), elseStr, 
              withStr, (f == level - 1), PreLinks, justCurrentSite);
        }
        else
        {
            int r = 0;
            while (r < cnt)
            {
                if (dg.Rows[r].Cells[5].Value != null)
                    if (dg.Rows[r].Cells[5].Value.ToString() == "p")
                    {
                        flag = true;
                        Application.DoEvents();
                        AddLinks(dg, dg.Rows[r].Cells[1].Value.ToString(), f + 1, 
                          GetFileSource(dg.Rows[r].Cells[1].Value.ToString()), 
                          elseStr, withStr, (f == level - 1), PreLinks, justCurrentSite);
                        dg.Rows.RemoveAt(r);
                        cnt--;
                    }
                    else
                        r++;
            }
            if (Maxcnt > 0 && r >= Maxcnt)
            {
                flag = false;
                break;
            }
        }
    }
}
链接数据结构的LinkItem数据类型
public struct LinkItem
{
    public string Href;
    public string Text;
    public override string ToString()
    {
        return Href + "\n\t" + Text;
    }
}
Forms 的重要方法:
方法 btnDelFileLinks_Click(object sender, EventArgs e):
用于过滤链接文件中的链接,此方法获取源链接文件名和目标链接文件,然后对源链接文件应用不需要的字符串列表和需要的字符串列表,过滤链接,然后将它们保存到目标文件。
使用此功能
- 在 URL 文本框中写入源文件名,或留空以通过打开对话框获取文件路径。
- 写入不需要的字符串,并用 / 字符分隔它们。
- 点击“从文件删除链接”按钮。
private void btnDelFileLinks_Click(object sender, EventArgs e)
{
try
{
if (txtElse.Text != "" || txtWith.Text != "")
{
    string[] elseStr = txtElse.Text.Split('/');
    string[] withStr = txtWith.Text.Split('/');
    if (txtURL.Text == "")
    {
        OpenFileDialog op = new OpenFileDialog();
        if (op.ShowDialog() == DialogResult.OK)
        {
            txtURL.Text = op.FileName;
        }
    }
    if (txtURL.Text != "")
    {
        SaveFileDialog sv = new SaveFileDialog();
        if (sv.ShowDialog() == DialogResult.OK)
        {
            StreamReader sr = new StreamReader(txtURL.Text);
            StreamWriter sw = new StreamWriter(sv.FileName);
            if (sr != null)
            {
                while (sr.EndOfStream == false)
                {
                    string li = sr.ReadLine();
                    bool flag = true;
                    for (int k = 0; k < elseStr.Length; k++)
                        if (li.Contains(elseStr[k]) == true && elseStr[k] != "")
                        {
                            flag = false;
                            break;
                        }
                    if (flag == true && withStr.Length > 0)
                    {
                        flag = false;
                        for (int m = 0; m < withStr.Length; m++)
                            if (li.Contains(withStr[m]) == true)
                            {
                                flag = true;
                                break;
                            }
                    }
                    if (flag == true)
                    {
                        sw.WriteLine(li);
                    }
                }
                sr.Close();
                sw.Close();
                MessageBox.Show("Exported to " + sv.FileName);
            }
        }
    }
}
else
    MessageBox.Show("No Condition !");
}
catch
{
MessageBox.Show("ٍerror");
}
}
方法 btnDiffLinks_Click(object sender, EventArgs e):
此方法用于在移除所选文件夹中存在的重复链接后,从源链接文件中导出新链接(仅顶级,通过选中“仅顶级”,或带有子文件夹,如果未选中)。
private void btnDiffLinks_Click(object sender, EventArgs e)
{
    OpenFileDialog opfd1 = new OpenFileDialog();
    if (opfd1.ShowDialog()==DialogResult.OK)
    {
        StreamReader sr = new StreamReader(opfd1.FileName);
        if (sr != null)
        {
            FolderBrowserDialog fbd=new FolderBrowserDialog();
            if (fbd.ShowDialog() == DialogResult.OK)
            {
                string inFolder = fbd.SelectedPath;
                SaveFileDialog sfd = new SaveFileDialog();
                if (sfd.ShowDialog() == DialogResult.OK)
                {
                    StreamWriter sw = new StreamWriter(sfd.FileName);
                    if (sw != null)
                    {
                        sw.AutoFlush = true;
                        string[] files = sr.ReadToEnd().Split(
                          new string[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
                        SortedList<string, string> SLfnames = new SortedList<string, string>();
                            string[] ExistFiles;
                            if (chIsJustInTop.Checked == true)
                                ExistFiles = Directory.GetFiles(inFolder, "*", SearchOption.TopDirectoryOnly);
                            else
                                ExistFiles = Directory.GetFiles(inFolder, "*", SearchOption.AllDirectories);
                        for(int i=0;i<ExistFiles.Length;i++)
                        {
                            ExistFiles[i] = Path.GetFileName(ExistFiles[i]);
                            if(ExistFiles[i]!="" && ExistFiles[i]!=null)
                              SLfnames.Add(ExistFiles[i].ToLower(),ExistFiles[i]);
                        }
                        for (int i = 0; i < files.Length; i++)
                        {
                            string fname = Path.GetFileName(files[i]);
                            if (fname != "" && fname != null)
                            {
                                if (SLfnames.IndexOfKey(fname.ToLower())<0)
                                        sw.WriteLine(files[i]);
                            }
                        }
                        sw.Close();
                    }
                }
            }
            sr.Close();
            MessageBox.Show("Successfully Done!");
        }
    }
}连接设置

连接到目标 URL 可能需要使用代理服务器,为此,请选中“使用代理服务器”复选框,并在第一个文本框中设置 IP,在第二个文本框中设置端口号。
 
要直接连接到目标 URL,请取消选中此选项。
注释:
- 当 dataGridView 的行不为空且您单击“获取链接”按钮时,URL 文本框的值仅为参考 URL,而不是起始点。
- 提取网站链接通常会花费大量时间,因此最大级别和最大计数非常重要。
- 在不需要的字符串和需要的字符串文本框中的字符串可以留空,或包含任何字符,包括空格或其他字符,但每个部分都用 /(斜杠)字符分隔。例如,字符串列表: 120/hello/direct game/book/@
- 在不需要的字符串或需要的字符串文本框中输入的字符串列表是区分大小写的。
- 此工具非常适合启用了目录列表的网站,特别是对于网站构建者。
关注点
我创建此工具是为了节省我的大量时间,并且我达到了我的目标,希望这对您也有用。
我的兴趣在于将一个复杂的下载工具与一个高智能的网页链接过滤工具相结合,以节省用户时间并获得最佳结果。
历史
Link manager 版本 1.0 于 2012 年实现。


